javax.validation 包里@NotNull等注解的使用
在做项目的时候,对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;
}