我们在日常开发中,避不开的就是参数校验,有人说前端不是会在表单中进行校验的吗?在后端中,我们可以直接不管前端怎么样判断过滤,我们后端都需要进行再次判断,为了安全。因为前端很容易拜托,当测试使用PostMan来测试,如果后端没有校验,不就乱了吗?肯定会有很多异常的。今天小编和大家一起学习一下JSR303专门用于参数校验的,算是一个工具吧!
JSR-303 是 JAVA EE 6 中的一项子规范,叫做 Bean Validation,官方参考实现是Hibernate Validator。
Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的实现,除此之外还有一些附加的 constraint。
官网介绍:
验证数据是一项常见任务,它发生在从表示层到持久层的所有应用程序层中。通常在每一层都实现相同的验证逻辑,这既耗时又容易出错。为了避免重复这些验证,开发人员经常将验证逻辑直接捆绑到域模型中,将域类与验证代码混在一起,而验证代码实际上是关于类本身的元数据。

Jakarta Bean Validation 2.0 - 为实体和方法验证定义了元数据模型和 API。默认元数据源是注释,能够通过使用 XML 覆盖和扩展元数据。API 不依赖于特定的应用程序层或编程模型。它特别不依赖于 Web 或持久层,并且可用于服务器端应用程序编程以及富客户端 Swing 应用程序开发人员。

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
| 约束注解名称 | 约束注解说明 |
|---|---|
| @Null | 用于验证对象为null |
| @NotNull | 用于对象不能为null,无法查检长度为0的字符串 |
| @NotBlank | 只用于String类型上,不能为null且trim()之后的size>0 |
| @NotEmpty | 用于集合类、String类不能为null,且size>0。但是带有空格的字符串校验不出来 |
| @Size | 用于对象(Array,Collection,Map,String)长度是否在给定的范围之内 |
| @Length | 用于String对象的大小必须在指定的范围内 |
| @Pattern | 用于String对象是否符合正则表达式的规则 |
| 用于String对象是否符合邮箱格式 | |
| @Min | 用于Number和String对象是否大等于指定的值 |
| @Max | 用于Number和String对象是否小等于指定的值 |
| @AssertTrue | 用于Boolean对象是否为true |
| @AssertFalse | 用于Boolean对象是否为false |
所有的大家参考jar包

@Validated:
@Valid:
总结:@Validated用到分组时使用,一个学校对象里还有很多个学生对象需要使用@Validated在Controller方法参数前加上,@Valid加在学校中的学生属性上,不加则无法对学生对象里的属性进行校验!
例子:
@Data
public class School{
@NotBlank
private String id;
private String name;
@Valid // 需要加上,否则不会验证student类中的校验注解
@NotNull // 且需要触发该字段的验证才会进行嵌套验证。
private List<Student> list;
}
@Data
public class Student {
@NotBlank
private String id;
private String name;
private int age;
}
@PostMapping("/test")
public Result test(@Validated @RequestBody School school){
}
import lombok.Data;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import java.io.Serializable;
@Data
public class BrandEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 品牌id
*/
@NotNull(message = "修改必须有品牌id")
private Long brandId;
/**
* 品牌名F
*/
@NotBlank(message = "品牌名必须提交")
private String name;
/**
* 品牌logo地址
*/
@NotBlank(message = "地址必须不为空")
private String logo;
/**
* 介绍
*/
private String descript;
/**
* 检索首字母
*/
//正则表达式
@Pattern(regexp = "^[a-zA-Z]$",message = "检索的首字母必须是字母")
private String firstLetter;
/**
* 排序
*/
@Min(value = 0,message = "排序必须大于等于0")
private Integer sort;
}
import com.alibaba.druid.util.StringUtils;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
//统一返回结果
@Data
@NoArgsConstructor
@AllArgsConstructor
@ApiModel
public class Result<T> {
@ApiModelProperty("响应码")
private Integer code;
@ApiModelProperty("相应信息")
private String msg;
@ApiModelProperty("返回对象或者集合")
private T data;
//成功码
public static final Integer SUCCESS_CODE = 200;
//成功消息
public static final String SUCCESS_MSG = "SUCCESS";
//失败
public static final Integer ERROR_CODE = 201;
public static final String ERROR_MSG = "系统异常,请联系管理员";
//没有权限的响应码
public static final Integer NO_AUTH_COOD = 999;
//执行成功
public static <T> Result<T> success(T data){
return new Result<>(SUCCESS_CODE,SUCCESS_MSG,data);
}
//执行失败
public static <T> Result failed(String msg){
msg = StringUtils.isEmpty(msg)? ERROR_MSG : msg;
return new Result(ERROR_CODE,msg,"");
}
//传入错误码的方法
public static <T> Result failed(int code,String msg){
msg = StringUtils.isEmpty(msg)? ERROR_MSG : msg;
return new Result(code,msg,"");
}
//传入错误码的数据
public static <T> Result failed(int code,String msg,T data){
msg = StringUtils.isEmpty(msg)? ERROR_MSG : msg;
return new Result(code,msg,data);
}
}
@PostMapping("/add")
public Result add(@Valid @RequestBody BrandEntity brandEntity) {
return Result.success("成功");
}
遇到的坑:小编在公司的项目中添加没什么问题,但是就是无法触发校验,看到的是Springboot版本太高了,所有要添加下面的依赖才触发。
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.18.Final</version>
</dependency>

@PostMapping("/add")
public Result add(@Valid @RequestBody BrandEntity brandEntity, BindingResult bindingResult){
if (bindingResult.hasErrors()){
Map<String,String> map = new HashMap<>();
bindingResult.getFieldErrors().forEach(item ->{
map.put(item.getField(),item.getDefaultMessage());
});
return Result.failed(400,"提交的数据不合规范",map);
}
return Result.success("成功");
}
{
"code": 400,
"data": {
"name": "品牌名必须提交",
"logo": "地址必须不为空"
},
"msg": "提交的数据不合规范"
}
上面我们要在每个校验的接口上面写,所以我们要抽离出来做个全局异常。并且要改进一下,原来的是把错误信息放到data里,但是正常情况下的data是返回给前端的数据。我们这样把异常数据放进去,会使data的数据有二义性。这样对于前端就不知道里面是数据还是报错信息了哈,这样就可以直接前端展示msg里面的提示即可!
import com.wang.test.demo.response.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@Slf4j
@RestControllerAdvice(basePackages = "com.wang.test.demo.controller")
public class ExceptionControllerAdvice {
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public Result handleVaildException(MethodArgumentNotValidException e){
log.error("数据校验出现问题:{},异常类型:{}",e.getMessage(),e.getClass());
BindingResult bindingResult = e.getBindingResult();
StringBuffer stringBuffer = new StringBuffer();
bindingResult.getFieldErrors().forEach(item ->{
//获取错误信息
String message = item.getDefaultMessage();
//获取错误的属性名字
String field = item.getField();
stringBuffer.append(field + ":" + message + " ");
});
return Result.failed(400, stringBuffer + "");
}
@ExceptionHandler(value = Throwable.class)
public Result handleException(Throwable throwable){
log.error("错误",throwable);
return Result.failed(400, "系统异常");
}
}
{
"code": 400,
"data": "",
"msg": "logo:地址必须不为空 name:品牌名必须提交 "
}
我们在做校验的时候,通常会遇到一个实体类的添加和修改,他们的校验规则是不同的,所以分组显得尤为重要。他可以帮助我们少建一个冗余的实体类,所以我们必须要会的。
public interface EditGroup {
}
public interface AddGroup {
}
/**
* 品牌id
*/
@NotNull(message = "修改必须有品牌id",groups = {EditGroup.class})
@Null(message = "新增不能指定id",groups = {AddGroup.class})
private Long brandId;
// 其余属性我们不变
注意:我们要进行分组,所以@Valid不能使用了,要使用@Validated。相信大家已经看到上面的他俩区别了哈!
@PostMapping("/add")
public Result add(@Validated({AddGroup.class}) @RequestBody BrandEntity brandEntity){
return Result.success("成功");
}
@PostMapping("/edit")
public Result edit(@Validated({EditGroup.class}) @RequestBody BrandEntity brandEntity){
return Result.success("成功");
}


import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;
//编写自定义的校验器
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {
private Set<Integer> set=new HashSet<Integer>();
//初始化方法
@Override
public void initialize(ListValue constraintAnnotation) {
int[] value = constraintAnnotation.vals();
for (int i : value) {
set.add(i);
}
}
/**
* 判断是否校验成功
* @param value 需要校验的值
* @param context
* @return
*/
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return set.contains(value);
}
}
@Documented
@Constraint(validatedBy = { ListValueConstraintValidator.class })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {
// 使用该属性去Validation.properties中取
String message() default "{com.atguigu.common.valid.ListValue.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
int[] vals() default {};
}
注意:我们上面做了分组,如果属性不指定分组,则不会生效,现在我们的部分属性校验已没有起作用,现在只有brandId和showStatus起作用。
/**
* 显示状态[0-不显示;1-显示]
*/
@NotNull(groups = {AddGroup.class, EditGroup.class})
@ListValue(vals = {0,1},groups = {AddGroup.class, EditGroup.class},message = "必须为0或者1")
private Integer showStatus;


这样就差不多对JSR303有了基本了解,满足基本开发没有什么问题哈!看到这里了,收藏点赞一波吧,整理了将近一天!!谢谢大家了!!
欢迎大家关注小编的微信公众号!!

有缘人才能看到,自己网站,欢迎访问!!!
exe应该在我打开页面时运行。异步进程需要运行。有什么方法可以在ruby中使用两个参数异步运行exe吗?我已经尝试过ruby命令-system()、exec()但它正在等待过程完成。我需要用参数启动exe,无需等待进程完成是否有任何rubygems会支持我的问题? 最佳答案 您可以使用Process.spawn和Process.wait2:pid=Process.spawn'your.exe','--option'#Later...pid,status=Process.wait2pid您的程序将作为解释器的子进程执行。除
我有一些Ruby代码,如下所示:Something.createdo|x|x.foo=barend我想编写一个测试,它使用double代替block参数x,这样我就可以调用:x_double.should_receive(:foo).with("whatever").这可能吗? 最佳答案 specify'something'dox=doublex.should_receive(:foo=).with("whatever")Something.should_receive(:create).and_yield(x)#callthere
我正在为一个项目制作一个简单的shell,我希望像在Bash中一样解析参数字符串。foobar"helloworld"fooz应该变成:["foo","bar","helloworld","fooz"]等等。到目前为止,我一直在使用CSV::parse_line,将列分隔符设置为""和.compact输出。问题是我现在必须选择是要支持单引号还是双引号。CSV不支持超过一个分隔符。Python有一个名为shlex的模块:>>>shlex.split("Test'helloworld'foo")['Test','helloworld','foo']>>>shlex.split('Test"
我不确定传递给方法的对象的类型是否正确。我可能会将一个字符串传递给一个只能处理整数的函数。某种运行时保证怎么样?我看不到比以下更好的选择:defsomeFixNumMangler(input)raise"wrongtype:integerrequired"unlessinput.class==FixNumother_stuffend有更好的选择吗? 最佳答案 使用Kernel#Integer在使用之前转换输入的方法。当无法以任何合理的方式将输入转换为整数时,它将引发ArgumentError。defmy_method(number)
两者都可以defsetup(options={})options.reverse_merge:size=>25,:velocity=>10end和defsetup(options={}){:size=>25,:velocity=>10}.merge(options)end在方法的参数中分配默认值。问题是:哪个更好?您更愿意使用哪一个?在性能、代码可读性或其他方面有什么不同吗?编辑:我无意中添加了bang(!)...并不是要询问nobang方法与bang方法之间的区别 最佳答案 我倾向于使用reverse_merge方法:option
我有一个只接受一个参数的方法:defmy_method(number)end如果使用number调用方法,我该如何引发错误??通常,我如何定义方法参数的条件?比如我想在调用的时候报错:my_method(1) 最佳答案 您可以添加guard在函数的开头,如果参数无效则引发异常。例如:defmy_method(number)failArgumentError,"Inputshouldbegreaterthanorequalto2"ifnumbereputse.messageend#=>Inputshouldbegreaterthano
我没有找到太多关于如何执行此操作的信息,尽管有很多关于如何使用像这样的redirect_to将参数传递给重定向的建议:action=>'something',:controller=>'something'在我的应用程序中,我在路由文件中有以下内容match'profile'=>'User#show'我的表演Action是这样的defshow@user=User.find(params[:user])@title=@user.first_nameend重定向发生在同一个用户Controller中,就像这样defregister@title="Registration"@user=Use
对于作为String#tr参数的单引号字符串文字中反斜杠的转义状态,我觉得有些神秘。你能解释一下下面三个例子之间的对比吗?我特别不明白第二个。为了避免复杂化,我在这里使用了'd',在双引号中转义时不会改变含义("\d"="d")。'\\'.tr('\\','x')#=>"x"'\\'.tr('\\d','x')#=>"\\"'\\'.tr('\\\d','x')#=>"x" 最佳答案 在tr中转义tr的第一个参数非常类似于正则表达式中的括号字符分组。您可以在表达式的开头使用^来否定匹配(替换任何不匹配的内容)并使用例如a-f来匹配一
我正在使用RubyonRails3.0.9,我想生成一个传递一些自定义参数的link_toURL。也就是说,有一个articles_path(www.my_web_site_name.com/articles)我想生成如下内容:link_to'Samplelinktitle',...#HereIshouldimplementthecode#=>'http://www.my_web_site_name.com/articles?param1=value1¶m2=value2&...我如何编写link_to语句“alàRubyonRailsWay”以实现该目的?如果我想通过传递一些
我有一个名为Post的类,我需要能够适应以下场景:如果用户选择了一个类别,则只显示该类别的帖子如果用户选择了一种类型,则只显示该类型的帖子如果用户选择了一个类别和类型,则只显示该类别中该类型的帖子如果用户没有选择任何内容,则显示所有帖子我想知道我的Controller是否不可避免地会因大量条件语句而显得粗糙...这是我解决此问题的错误方法-有谁知道我如何才能做到这一点?classPostsController 最佳答案 您最好遵循“胖模型,瘦Controller”的惯例,这意味着您应该将这种逻辑放在模型本身中。Post类应该能够报告