JSR-303 实现参数校验

参考:https://blog.csdn.net/qq_45076180/article/details/106219684
参考:https://zhuanlan.zhihu.com/p/97555913
参考:https://blog.csdn.net/u013934408/article/details/108872775
参考:https://blog.csdn.net/adparking/article/details/112918333

公众号文章:https://mp.weixin.qq.com/s/VNrs1zGPgx9GsIqXEDZP0Q

1. 什么是JSR-303

JSR-303 是 JAVA EE 6 中的一项子规范,叫做 Bean Validation,官方参考实现是Hibernate Validator。

它是一个hibernete独立的jar包,所以使用这个jar包并不需要一定要集成Hibernete框架。

JSR-303 用于对 Java Bean 中的字段的值进行验证。

Spring MVC 3.x 之中也大力支持 JSR-303,可以在控制器中对表单提交的数据方便地验证。

JSR-303官网



2. JSR-303内置校验规则





3. SpringBoot整合JSR-303

3.1 导入依赖

<dependency> 
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

3.2 在类的属性上加上相应校验注解

@NotNull(message="名字不能为空")
private String userName;

@Max(value=120,message="年龄最大不能查过120")
private int age;

@Email(message="邮箱格式错误")
private String email;

3.3 实现校验

在Controller的处理方法的参数前,加@Validated注解,出错后,错误信息会放置在 Errors或BindingResult 的对象参数中

@RequestMapping("/login")
public String testValid(@Validated User user, BindingResult result){
    if (result.hasErrors()){
         List<ObjectError> errorList = result.getAllErrors();
         for(ObjectError error : errorList){
         System.out.println(error.getDefaultMessage());
      }
    }           
  return "test";
}

3.4 通过全局统一异常替换BindingResult写法

3.3那种做法给controller后的需要校验的bean后加一个BindingResult类就可以获得校验的结果,但是相对麻烦,每次都要写BindingResult。在大项目中一般使用统一异常处理校验结果,返回json格式,如下所示。这样就不需要在controller加一个BindingResult类。

//后端校验异常
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
public  RtnResult handValidException(MethodArgumentNotValidException e){
	BindingResult bindingResult = e.getBindingResult();
	Map<String, Object> map = new HashMap<>();
	bindingResult.getFieldErrors().forEach(fieldError -> {
		map.put(fieldError.getField(),fieldError.getDefaultMessage());
	});
	return RtnResult.fail(map);
}








3.5 分组校验(多场景的复杂校验)

分组校验应用场景:添加和更新需要不同的校验规则时,且你更新和添加用的实体都是同一个时。有些项目会要求必须新增实体是一个,更新实体是一个,那么这种情况下就不考虑这种。

3.5.1 先在你的实体上创建添加和更新的接口,该接口仅仅是多场景的一个标识,接口内容为空即可。
public interface AddGroup {
}

public interface UpdateGroup {
}
3.5.2 在 @NotBlank(message = “密码不能为空”) 后加上groups 用于表示给校验注解标注什么情况下需要校验(添加/更新)
//AddGroup.class是添加的标识组,可以有多个
@NotBlank(message = "密码不能为空", groups = {AddGroup.class})
private String password;

@NotBlank(message = "姓名不能为空", groups = {AddGroup.class,UpdateGroup.class})
private String name;
3.5.3 必须使用@Validated注解制定只是用哪个操作,否则不生效。
//添加用户   @Validated指定哪个操作使用校验
@PostMapping("/addUser")
public RtnResult addUser(@Validated(AddGroup.class) @RequestBody ClaimUser claimUser){
     return  claimUserService.addUser(claimUser);
}

如果使用分组校验,校验注解没有使用groups,则校验不生效,一定要使用groups!!


4. 自定义校验注解

当给出的校验规则不能满足需求,可以通过自定义校验来实现,如某个字段只能为0或1。
举例场景:当显示和隐藏的属性showStatus只能时0或1,需要自定义校验注解和自定义校验器

4.1 导入依赖

<dependency>
     <groupId>javax.validation</groupId>
     <artifactId>validation-api</artifactId>
     <version>2.0.1.Final</version>
</dependency>

4.2 编写自定义校验注解

PS:可以参考JSR-303原本的校验

@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)   //运行环境
@Repeatable(List.class)
@Documented
@Constraint(validatedBy = { })  // 指定自定义校验器,可以适配多个校验器,一个注解完成多种校验
public @interface NotNull {
      String message() default "{javax.validation.constraints.NotNull.message}"; // 校验失败的提示信息
      Class<?>[] groups() default { };
      Class<? extends Payload>[] payload() default { };
      int[]  vals() default {};  //标注在属性上的vals={0,1}
        
}

4.3 编写自定义校验注解

//自定义校验器 必须实现ConstraintValidator
//ConstraintValidator中的 泛型1:绑定的泛型注解   泛型2:校验的数据类型
public class ListValueConstraintValidator implements ConstraintValidator<ListValue, Integer> {

    private Set<Integer> set = new HashSet<>();

    //初始化方法,获取在Entity类的属性上标注的符合条件的Integer的值:{0,1}
    @Override
    public void initialize(ListValue constraintAnnotation) {
        int[] vals = constraintAnnotation.vals();
        for (int val : vals) {
            set.add(val);
        }
    }

    //判断是否校验成功方法
    //integer:传进来的值
    //判断依据:看integer是否在初始化方法的数组中
    @Override
    public boolean isValid(Integer integer, ConstraintValidatorContext constraintValidatorContext) {
        if (set.contains(integer)) {
            return true;
        }
        return false;
    }
}

4.4 在需要校验的属性上添加自定义注解

@ListValue(vals={0,1},message = "开关状态只能是0或1")
private Integer showStatus;

4.5 自定义参数校验(这边以校验手机号为例)

  1. 编写注解类
//说明该注解将被包含在javadoc中
@Documented
// 注解的作用目标 类上、方法上、属性上
@Target({ElementType.FIELD, ElementType.PARAMETER})
// 注解的保留位置  
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {IsMobileValidator.class }) // 与约束注解关联的验证器
public @interface IsMobile {

    boolean required() default true;

    String message() default "手机号不正确";

    Class<?>[] groups() default { };

    Class<? extends Payload>[] payload() default { };

}
  1. 编写校验规则
public class IsMobileValidator implements ConstraintValidator<IsMobile, String> {
    private boolean required;

    /**
     * 重写initialize方法获取注解实例
     * @param ca
     */
    @Override
    public void initialize(IsMobile ca) {
        // 重注解实例中获信息
        required = ca.required();
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        // value就是要校验的数据了
        if (value != null && required) {
            // 手机号校验规则
            System.out.println(value);
            String regexp= "^(((\+\d{2}-)?0\d{2,3}-\d{7,8})|((\+\d{2}-)?(\d{2,3}-)?([1][3,4,5,7,8][0-9]\d{8})))$";
            boolean matches = Pattern.matches(regexp, value);
            System.out.println(matches);
            return matches;
        }
        return false;
    }
}
  1. 使用自定义校验注解
    /**
     * 手机号
     */
    @IsMobile(message = "用户手机号不正确")
    private String tel;
原文地址:https://www.cnblogs.com/itlihao/p/14280903.html