Bootstrap

SpringBoot使用注解校验参数

内置注解参数校验

引入依赖

springboot 2.3之前的版本只需要引入web,之后的版本还需要引入validation组件。

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

常用注解

注解说明
@AssertFalse值必须为false或null
@AssertTrue值必须为true或null
@DecimalMax必须为数字,且不能超过指定的最大值
@DecimalMin必须为数字,且不能小于指定的最小值
@Digits必须为数字,且位数必须在指定范围内
@Email值必须符合email格式
@Max必须不大于指定的最大值
@Min必须不小于指定的最小值
@NotNull值不能为null
@NotBlank只适用于字符串,值不能为null,且字符串使用trim()后也不能为空
@NotEmpty值不能为null,字符串不能为空,数组、集合、map等大小不能为0
@Pattern值必须满足指定的正则表达式
@Positive值必须为正整数
@Size元素大小必须在指定范围内

使用方式

校验的注解有@Validated或@Valid,常见的校验的场景有以下两种:

  • 校验实体结构中的字段

    @Data
    public class User {
    
        @NotEmpty(message = "user name cannot be null or empty.")
        private String name;
    
        @Positive(message = "user age must larger than zero.")
        private int age;
    
        @Email(message = "email format must be correct.")
        private String email;
    }
    
  • 在Controller层直接校验请求参数

    @GetMapping("/demo/users/{name}")
    public User getUserByName(@PathVariable(value = "name") 
                              @Size(min = 1, max = 20)  String name) {
        return userService.getUserByName(name);
    }
    

要使得上述注解生效,只需要在Controller层做如下处理:

  • 对于实体类User的校验,可以直接在Controller类上面添加@Validated注解,或者方法中字段前添加@Validated或@Valid注解。

    //类和方法上只要有一个地方使用@Validated即可
    //@Validated
    @RestController
    public class UserController {
    
        @Autowired
        private UserService userService;
    
        //方法上也可以使用@Validated
        @PostMapping(value = "/demo/users")
        public void createUser(@RequestBody @Valid User user) {
            userService.createUser(user);
        }
    }
    

    此外,@Validated和@Valid注解也可以放在UserService的接口中生效。

  • 对于Controller层请求参数的校验,需在Controller类上添加@Validated注解。

    @Validated
    @RestController
    public class UserController {
    
        @Autowired
        private UserService userService;
    
        @GetMapping("/demo/users/{name}")
        public User getUserByName(@PathVariable(value = "name")
                                  @Size(min = 3, max = 4) String name) {
            return userService.getUserByName(name);
        }
    }
    

@Validated和@Valid

这两个注解都可以使参数校验生效,但也有些区别:

区别@Validated@Valid
依赖org.springframework.validation.annotation.Validatedjavax.validation.Valid
作用目标作用于类、方法和参数上,不能用于字段上作用于方法、字段、构造函数、参数上
分组校验支持不支持
嵌套校验不支持单独的嵌套校验,但可以和@Valid配合使用(@Valid用于字段上)不支持单独的嵌套校验,但支持和@Valid或@Validated配合使用

嵌套校验

@Validated和@Valid注解都不具备单独进行嵌套校验的功能。

所谓的嵌套校验,就是需要校验某个实体类中定义的其他实体。例如,假设User类中定义了一个Family实体类的成员变量family,则需要在family字段上面添加@Valid注解才能使对family的校验生效。

@Data
public class User {
    @NotEmpty(message = "user name cannot be null or empty.")
    private String name;

    @Positive(message = "user age must larger than zero.")
    private int age;

    @Email(message = "email format must be correct.")
    private String email;

    //添加@Valid注解才能使Family实体中对address的校验注解生效
    @Valid
    private Family family;
}


@Data
public class Family {
    @NotBlank(message = "address must be not blank.")
    private String address;

    private int memberNum;
}

这样,只要在Controller层对User类参数添加@Validated或@Valid注解即可。

自定义注解参数校验

当内置的注解无法满足对参数的校验场景时,可以自定义注解进行参数校验。实现自定义注解参数校验通常分为以下三步:

​ 1)编写自定义注解

​ 2)实现注解校验类

​ 3)使用自定义注解

编写自定义注解

假设现在需要校验一个User类用户是否已成年,可以自定义一个这样的校验注解:

@Documented
@Target({TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Constraint(validatedBy = {AdultUserValidator.class})
public @interface AdultUserValidation {

    String message() default "";

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

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


    @Target({TYPE, CONSTRUCTOR, PARAMETER})
    @Retention(RUNTIME)
    @Documented
    public @interface List {
        AdultUserValidation[] value();
    }
}

相关注解介绍:

注解用法说明备注
@Documented被此注解修饰的类,在生成文档时,会显示该类的内容
@Target通过ElementType指定该注解可以作用哪些目标元素上ElementType介绍详见下文
@Retention保留注解的位置,由RetentionPolicy指定RetentionPolicy介绍详见下文
@Constraint用于限定自定义注解的使用范围,通过validatedBy指定实现注解校验的类

ElementType介绍:

ElementType是一个枚举类型,配合@Target注解用于指定注解可以使用在哪些目标元素上,具体枚举选项如下:

选项说明
ElementType.TYPE该注解可以作用在类、接口、枚举上
ElementType.FIELD该注解只能作用在属性字段上
ElementType.METHOD该注解只能作用在方法上
ElementType.PARAMETER该注解只能作用在方法的参数上
ElementType.CONSTRUCTOR该注解只能作用在类的构造器上
ElementType.LOCAL_VARIABLE该注解只能作用在局部变量上
ElementType.ANNOTATION_TYPE该注解只能作用在注解上
ElementType.PACKAGE该注解只能作用在包上
ElementType.TYPE_PARAMETER该注解可以作用在类的参数上
ElementType.TYPE_USE该注解可以作用在类的任何语句上

RetentionPolicy介绍:

通常,注解的生命周期分为三个阶段:Java源文件 -> class文件 -> 内存中的字节码,这三个阶段分别可以通过RetentionPolicy的三个枚举类型指定,配合@Retention注解指定注解保留在什么地方,具体枚举选项如下:

选项说明
RetentionPolicy.SOURCE注解只被保留在源文件阶段,编译时会被丢弃
RetentionPolicy.CLASS注解被保留在class文件阶段,但不会被加载到JVM中
RetentionPolicy.RUNTIME注解被保留在源文件、class文件中,运行时会被加载到JVM中

实现注解校验类

实现@AdultUserValidation注解中的AdultUserValidator类如下:

public class AdultUserValidator implements ConstraintValidator<AdultUserValidation, User> {

    @Override
    public void initialize(AdultUserValidation constraintAnnotation) {
        //Do some initialization work before validation.
    }

    @Override
    public boolean isValid(User user, ConstraintValidatorContext context) {

        if (user == null) {
            return false;
        }

        return isAdultUser(user);
    }

    public boolean isAdultUser(User user) {
        return !user.getName().isEmpty() && user.getAge() >= 18;
    }
}

使用自定义注解

自定义校验注解的使用和内置注解的使用方式基本一致,在需要使用自定义注解的地方添加该注解,然后再Controller层添加@Validated或@Valid注解使其生效。

通常,对字段的校验需要在自定义注解的@Target中添加FIELD,然后在对应的字段上添加该注解;对实体类(例如User类)整体进行校验的注解会在注解的@Target中添加TYPE,然后就可以在User类前面添加自定义的注解

@AdultUserValidation
public class User {
  ...
}

自定义注解属性

在定义注解时,还可以根据需要来定义属性来实现特定的校验场景。例如,假设在User类中定义了一个gender字段来指定用户性别,gender字段只有Male和Female两个可选字段。

自定义一个@GenderValidation注解来校验gender字段,注解定义如下:

@Documented
@Target({FIELD, PARAMETER})
@Retention(RUNTIME)
@Constraint(validatedBy = {GenderValidator.class})
public @interface GenderValidation {

    String message() default "Gender must be Male or Female.";

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

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

    //自定义属性
    //校验字段的值必须在注解中指定的数组中才能校验通过
    String[] value() default {};


    @Documented
    @Target({TYPE, CONSTRUCTOR, PARAMETER})
    @Retention(RUNTIME)
    public @interface List {
        GenderValidation[] value();
    }
}

注解校验类GenderValidator如下:

public class GenderValidator implements ConstraintValidator<GenderValidation, String> {
    private List<String> genders;

    @Override
    public void initialize(GenderValidation constraintAnnotation) {
        this.genders = Arrays.asList(constraintAnnotation.value());
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        return genders.contains(value);
    }
}

然后,可以在User类的gender字段上添加注解@GenderValidation,如下所示:

@Data
public class User {
    ...
    //value属性指定gender的可选项只能为"Male"和"Female",否则校验不通过
    @GenderValidation(value = {"Male", "Female"}, 
                      message = "user gender must be Male or Female.")
    private String gender;
}

分组校验

@Validated和@Valid注解的区别之一就是前者支持分组校验。所谓的分组校验,可以理解为按照不同的分组或场景分别对相应的参数进行校验。要实现分组校验,可以分以下三步进行:

​ 1)定义分组接口或类

​ 2)在待校验参数的注解上指定分组类

​ 3)在Controller层添加@Validated注解并指定分组类

定义分组接口

定义以下两个分组校验接口,分别用于创建用户和更新用户时的校验。

public interface Create extends Default {
}

public interface Update extends Default {
}

这里继承了javax.validation.groups.Default接口,表示在进行Create/Update分组校验时,其他使用了javax.validation的Default分组的注解的地方也会生效

校验参数注解上指定分组类

User类的修改如下,参数name字段添加Create.class分组,参数email字段添加Update.class分组,通过注解的属性groups来指定。

@Data
public class User {

    @NotEmpty(message = "user name cannot be null or empty.", groups = Create.class)
    private String name;

    @Positive(message = "user age must larger than zero.")
    private int age;

    @Email(message = "email format must be correct.", groups = Update.class)
    private String email;
}

Controller层添加@Validated注解并指定分组类

@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @PostMapping(value = "/demo/users")
    public void createUser(@RequestBody @Validated(Create.class) User user) {
        userService.createUser(user);
    }
    
    @PutMapping(value = "/demo/users")
    public void updateUser(@RequestBody @Validated(Update.class) User user) {
        userService.updateUser(user);
    }
}

至此,可以通过调用创建用户和更新用户接口进行测试。例如,通过验证可知,在调用createUser时,email字段不做校验,即不按照email格式仍然可以创建成功,而在调用updateUser时,该字段校验生效。

;