springboot 2.3之前的版本只需要引入web,之后的版本还需要引入validation组件。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-validationartifactId>
dependency>
注解 | 说明 |
---|---|
@AssertFalse | 值必须为false或null |
@AssertTrue | 值必须为true或null |
@DecimalMax | 必须为数字,且不能超过指定的最大值 |
@DecimalMin | 必须为数字,且不能小于指定的最小值 |
@Digits | 必须为数字,且位数必须在指定范围内 |
值必须符合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 |
---|---|---|
依赖 | org.springframework.validation.annotation.Validated | javax.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;
}
@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时,该字段校验生效。