
在做项目的时候,对pojo和传入的参数进行校验,如果是代码编写,需要很多if来判断
其实可根据一些校验的注解来实现我们的参数校验,主要介绍一下常用的 javax.validation 这个仓库的使用,这里总结一下
1、导包在项目的pom.xml 文件夹中导入包
javax.validation
validation-api
1.1.0.Final
这个验证根据JSR 380规范,validation-api 依赖包含标准验证注解
2、使用这个包的注解主要有以下这么多,先简单说明(以版本 号2.0.2为标准),下面再分别介绍
// 表明注解可以使用在哪里
// 这里表示注解可以使用在 方法 属性 注解 构造方法 参数 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为这个组别的属性就不生效了)
@PostMapping("/users/update")
public ResponseDTO updateUser(@RequestBody @Validated(UpdateUser.class) User user) {
userService.updateById(userDTO);
return ResponseDTO.success();
}
@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());
}
@ExceptionHandler(ValidationException.class)
public ResponseDTO handlevalidationException(ValidationException e) {
logger.error(e.getMessage(), e);
return new ResponseDTO(VALIDATION_CODE, e.getCause().getMessage());
}
@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 AbstractMaxValidatorimplements ConstraintValidator { 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{ @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;
}
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)