javax.validation包里@NotNull等注解的使用方式
作者:pan_mlpan
在做项目的时候,对pojo和传入的参数进行校验,如果是代码编写,需要很多if来判断
其实可根据一些校验的注解来实现我们的参数校验,主要介绍一下常用的 javax.validation 这个仓库的使用,这里总结一下
1、导包
在项目的pom.xml 文件夹中导入包
<!-- https://mvnrepository.com/artifact/javax.validation/validation-api --> <dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> <version>1.1.0.Final</version> </dependency>
这个验证根据JSR 380规范,validation-api 依赖包含标准验证注解
2、使用
这个包的注解主要有以下这么多,先简单说明(以版本 号2.0.2为标准)
下面再分别介绍
注解名称 | 验证得类型 | 描述 |
---|---|---|
AssertFalse | Boolean,boolean | 被注解的元素属性值必须为false |
AssertTrue | Boolean,boolean | 被注解的元素属性值必须为true |
DecimalMax | BigDecimal,BigInteger, byte,short, int, long,等任何Number或CharSequence(存储的是数字)子类型 | 验证注解的元素值小于等于指定的value值 |
DecimalMin | BigDecimal,BigInteger, byte,short, int, long,等任何Number或CharSequence(存储的是数字)子类型 | 验证注解的元素值大于等于指定的value值 |
Digits | BigDecimal,BigInteger, byte,short, int, long,等任何Number或CharSequence(存储的是数字)子类型 | 验证注解的元素值的整数位数和小数位数上限 |
Max | BigDecimal,BigInteger, byte,short, int, long,等任何Number或CharSequence(存储的是数字)子类型 | 被注解的元素值的类型必须为数字,其值必须小于等于指定的最大值 |
Min | BigDecimal,BigInteger, byte,short, int, long,等任何Number或CharSequence(存储的是数字)子类型 | 被注解的元素值的类型必须为数字,其值必须大于等于指定的最小值 |
CharSequence子类型(如String) | ||
Future | java.util.Date,java.util.Calendar;Joda Time类库的日期类型 | 被注解的元素值得范围必须为未来的一个时间 |
FutureOrPresent | java.util.Date,java.util.Calendar;Joda Time类库的日期类型 | 被注解的元素值得范围必须为未来的一个时间或者现在得时间 |
Past | java.util.Date,java.util.Calendar;Joda Time类库的日期类型 | 被注解的元素的值范围必须为过去的一个时间 |
PastOrPresent | java.util.Date,java.util.Calendar;Joda Time类库的日期类型 | 被注解的元素的值范围必须为过去或者现在的一个时间 |
Negative | 数值 | 适用于数值并验证它们是严格负数,不包括0 |
NegativeOrZero | 数值 | 适用于数值并验证它们是严格负数,包括0 |
NotBlank | CharSequence子类型 | 验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的首位空格 |
NotEmpty | CharSequence子类型、Collection、Map、数组 | 验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0) |
NotNull | 任意类型 | 被注解的元素属性值必须非null |
Null | 任意类型 | 被注解的元素属性值必须为null |
Pattern | String,任何CharSequence的子类型 | 被注解的元素值必须符合指定的正则表达式 |
Positive | 数值 | 适用于数值并验证它们是严格正数,不包括0 |
PositiveOrZero | 数值 | 适用于数值并验证它们是严格正数,包括0 |
Size | String, Collection, Map和数组属性 | 被注解的元素属性值的大小必须在指定范围内 |
2.0 注解的介绍
// 表明注解可以使用在哪里 // 这里表示注解可以使用在 方法 属性 注解 构造方法 参数 type @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE}) //描述保留注解的各种策略,它们与元注解(@Retention)一起指定注释要保留多长时间 @Retention(RetentionPolicy.RUNTIME) //表明这个注解是由 javadoc记录的 @Documented public @interface MyOwnAnnotation { }
2.1 注解详细属性的介绍
查看上面所有注解的源码,关于注解中的字段,主要分为以下两种
2.1.1 不能自定义值的
以下面的这些注解为代表
- AssertFalse AssertTrue
- PositiveOrZero Positive Negative NegativeOrZero
- NotNull Null NotEmpty NotBlank
- FutureOrPresent Future PastOrPresent Past
以 @AssertFalse为例,该注解的详细内容(源码)
@Documented @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE}) @Retention(RetentionPolicy.RUNTIME) // 这个表明可以重复使用, //例如在一个field使用这个注解 // 一般注解只能使用一次 //@Max(value = 11) //Long num; // 如果使用了这个@Repeatable标注,那就可以使用两次 //@Max(value = 11) //@Max(value = 22) //Long num; @Repeatable(AssertFalse.List.class) // 这个Constraint 指定验证类,如果不自定义指定,实现其默认的验证类 ,如果指定 // @Constraint(validatedBy = { MyValidator.class }) @Constraint(validatedBy = {}) public @interface AssertFalse { // 校验失败的信息,可以自定义 String message() default "{javax.validation.constraints.AssertFalse.message}"; // 这里主要进行将validator进行分类,不同的类group中会执行不同的validator操作 Class<?>[] groups() default {}; // 这个主要是指定bean,一般很少使用 Class<? extends Payload>[] payload() default {}; @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface List { AssertFalse[] value(); } }
2.1.2 能自定义值的
其他的注解都是可以自定义一些值,作为校验的参照值
以 @Max为例
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE}) @Retention(RetentionPolicy.RUNTIME) @Repeatable(Max.List.class) @Documented @Constraint(validatedBy = {}) public @interface Max { // 校验失败的信息,可以自定义 String message() default "{javax.validation.constraints.Max.message}"; // 这里主要进行将validator进行分类,不同的类group中会执行不同的validator操作 Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; // 与上面不同的就是多了个value,这个需要自定义参数传入的值与这个value值对比,也就是校验的参考值 long value(); @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface List { Max[] value(); } }
2.1.3 groups属性的使用
这个属性的作用就是举个例子:例如我们在传参的时候,加入我们创建一个用户,那么我们再传入的参数是不会包含用户的id,而如果更新这个用户,我们就需要传入用户id,那么根据多种校验规则的时候,这个属性就起作用了。
如果我们在一个属性使用注解的时候,如果不指定groups的时候,其实默认是在Default 组别中
@NotNull(message = "姓名不能为空") private String name; 等同于 import javax.validation.groups.Default; @NotNull(message = "姓名不能为空" , groups = {Default.class}) private String name;
那么根据这个情况,如果我们想自定义组别的时候,我们就可以分为以下几个步骤:
- 定义组别
- 使用组别
(1)定义组别
还是这个例子:如果创建一个用户,不用传入用户的id,如果更新这个用户,我们就需要传入用户id
那么我们就定义两个组别,创建用户组别、更新用户组别(其实就是两个接口,里面不用有方法,但是要注意要继承Default接口,理由下面会说)
import javax.validation.groups.Default; public interface CreateUser extends Default { } import javax.validation.groups.Default; public interface UpdateUser extends Default{ }
(2)使用组别
1)首先在pojo中,我们需要使用组别,若属性在不同的组别有不同的校验方式,那么就特殊指定其需要校验的规则,如果不指定,就还是默认按照Default
这里需要注意:定义的组别最好要继承Default接口,不然当我们在指定规则的校验时候,那么不标注groups的属性就不再校验了,因为默认按照Default
@Data class User{ @NotNull(message = "用户id不能为空", groups = UpdateUser.class) private Integer id; @NotNull(message = "姓名不能为空" ,groups = {CreateUser.class, UpdateUser.class}) private String name; @NotNull(message = "性别不能为空") private String sex; @Max(value = 20 ,message = "最大长度为20") private String address; @Email(message = "不满足邮箱格式") private String email; @AssertTrue(message = "字段为true才能通过") private boolean isAuth; @NotBlank(message = "手机号不能为空") @Pattern(regexp = "^[1][3,4,5,6,7,8,9][0-9]{9}$", message = "手机号格式有误") private String mobile; @Future(message = "时间在当前时间之后才可以通过") private Date date; }
2)在接口中使用特定的组别。
两个接:
- 创建user的时候,指定验证的组别是CreateUser
- 更新user的时候,指定验证的组别是UpdateUser
(当自定义的组别继承了Default,这里指定验证组别的,属性会根据指定的groups来进行校验,那么没有指定groups的都会校验,如果不继承Default,那没有自定义groups为这个组别的属性就不生效了)
/** * 走参数校验注解的 groups 组合校验 * * @param user * @return */ @PostMapping("/users/update") public ResponseDTO updateUser(@RequestBody @Validated(UpdateUser.class) User user) { userService.updateById(userDTO); return ResponseDTO.success(); } /** * 走参数校验注解的 groups 组合校验 * * @param user * @return */ @PostMapping("/users/save") public ResponseDTO saveUser(@RequestBody @Validated(CreateUser.class) User user) { userService.saveUser(userDTO); return ResponseDTO.success(); }
3、使用
3.1 第一种方式,pojo作为传参的形式
- Pojo 定义验证规则以
- pojo作为参数传参
(1)User pojo类 定义校验规则
@Data class User{ @NotNull(message = "姓名不能为空") private String name; @NotNull(message = "性别不能为空") private String sex; @Max(value = 20 ,message = "最大长度为20") private String address; @Email(message = "不满足邮箱格式") private String email; @AssertTrue(message = "字段为true才能通过") private boolean isAuth; @NotBlank(message = "手机号不能为空") @Pattern(regexp = "^[1][3,4,5,6,7,8,9][0-9]{9}$", message = "手机号格式有误") private String mobile; @Future(message = "时间在当前时间之后才可以通过") private Date date; }
(2)让校验规则生效。在Controller类的时候,我们只需要利用 @Validated 注解来实现pojo的校验
@RequestMapping("users") public ResponseDTO saveUser( @RequestBody @Validated User user){ }
(3)捕捉验证异常。如果参数校验通过,那么就直接执行接口方法,但是如果失败了,我们如何进行处理
MethodArgumentNotValidException
是springBoot中进行绑定参数校验时的异常,需要在springBoot中处理
其他需要处理 ConstraintViolationException异常进行处理
一般像异常捕捉的,可以自定义一个异常捕捉Handler,实现异常捕捉后的数据返回
import com.dto.ResponseDTO; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.dao.DuplicateKeyException; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.servlet.NoHandlerFoundException; import javax.validation.ConstraintViolationException; import javax.validation.ValidationException; @RestControllerAdvice public class GlobalExceptionHandler { private Logger logger = LoggerFactory.getLogger(getClass()); private static int DUPLICATE_KEY_CODE = 1001; private static int PARAM_FAIL_CODE = 1002; private static int VALIDATION_CODE = 1003; /** * 处理自定义异常 */ @ExceptionHandler(BizException.class) public ResponseDTO handleRRException(BizException e) { logger.error(e.getMessage(), e); return new ResponseDTO(e.getCode(), e.getMessage()); } /** * 方法参数校验 */ @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseDTO handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { logger.error(e.getMessage(), e); return new ResponseDTO(PARAM_FAIL_CODE, e.getBindingResult().getFieldError().getDefaultMessage()); } /** * ValidationException */ @ExceptionHandler(ValidationException.class) public ResponseDTO handleValidationException(ValidationException e) { logger.error(e.getMessage(), e); return new ResponseDTO(VALIDATION_CODE, e.getCause().getMessage()); } /** * ConstraintViolationException */ @ExceptionHandler(ConstraintViolationException.class) public ResponseDTO handleConstraintViolationException(ConstraintViolationException e) { logger.error(e.getMessage(), e); return new ResponseDTO(PARAM_FAIL_CODE, e.getMessage()); } /** * 路径异常 */ @ExceptionHandler(NoHandlerFoundException.class) public ResponseDTO handlerNoFoundException(Exception e) { logger.error(e.getMessage(), e); return new ResponseDTO(404, "不好意思,路径不存在,请检查路径是否正确"); } /** * 所有其他异常捕捉 */ @ExceptionHandler(Exception.class) public ResponseDTO handleException(Exception e) { logger.error(e.getMessage(), e); return new ResponseDTO(500, "不好意思,系统繁忙,请稍后再试"); } }
3.2 第二种方式,restful风格
- Controller 上加上@Validated
- 参数上加注解
@RestController @RequestMapping("user/") @Validated public class UserController{ @RequestMapping("users) public ResponseDTO getUser(@RequestParam("userId") @NotNull(message = "用户id不能为空") Long userId){ } }
4、自定义使用
如果我们想自定义一个验证的注解,那么需要怎么做呢?
- 定义一个注解
- 编写一个验证类
- 使用
**(1)**我们首先定义一个像上面的@Null 这样的注解
@Documented // 这里标注该注解是使用在filed和参数上的 @Target({ElementType.PARAMETER, ElementType.FIELD}) // 运行时生效 @Retention(RetentionPolicy.RUNTIME) // 指定验证的类是哪个 MyValidator 就是第二步做的事情 @Constraint(validatedBy = MyValidator.class) public @interface MyValid { // 提供自定义异常的信息,可以指定默认值 String message() default "参数不合法"; // 分组,详细看 上面的介绍 Class<?>[] groups() default {}; // 针对bean的,使用不多 Class<? extends Payload>[] payload() default {}; }
(2)编写一个验证类
我们需要编写一个 实现 ConstraintValidator 类实现类,来指定我们的校验规则
如果不指定特定注解的情况下,直接使用
// 这个是Max的指定的验证规则源码 public abstract class AbstractMaxValidator<T> implements ConstraintValidator<Max, T> { protected long maxValue; public AbstractMaxValidator() { } public void initialize(Max maxValue) { this.maxValue = maxValue.value(); } public boolean isValid(T value, ConstraintValidatorContext constraintValidatorContext) { if (value == null) { return true; } else { return this.compare(value) <= 0; } } protected abstract int compare(T var1); } // 自定义的验证类 public class MyValidator implements ConstraintValidator { @Override public void initialize(Annotation constraintAnnotation) { // 可以获取注解的值 ,一般写在该方法中 } @Override public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) { // 编写自己属性验证的规则,o 则是待验证的值 return false; } }
如果在指定特定注解的情况下,那么我们就可特定 注解
// 自定义的验证类 public class MyValidator implements ConstraintValidator<MyValid , Object> { @Override public void initialize(MyValid myValid) { // 可以获取注解的值 ,一般写在该方法中 } @Override public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) { // 编写自己属性验证的规则,o 则是待验证的值 return false; } }
(3)使用。就跟正常的那些注解一样使用即可。
@Data public Class Example{ @NotBlank(message = "姓名不能为空") @MyValid(message = "姓名有误,请核对后提交") private String name; }
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。