1、简介
Spring Boot支持JSR-303验证框架,默认实现是Hibernate Validator,项目中只要在Java Bean上放一些校验注解,就可以实现校验支持,杜绝通篇 if else 参数判断,而且这个校验是支持group的概念的,对于不同的group生效的校验不一样。这个很有用,因为对于增删改查等不同的操作,需要执行的校验本来就是不一样的。
注意:
spring boot2.3.x版本的spring boot项目,在使用hibernate-validator相关的注解的时候,发现不能导入相关的包。
官方给出的解决方法,手动导入相关依赖就行,如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
2、Validator常见注解清单
注释 | 描述 |
---|---|
@AssertFalse | 被注释的参数必须为 false |
@AssertTrue | 被注释的元素必须为 true |
@NotNull | 参数不能是Null |
@Null | 参数是Null |
@Szie | 带注释的参数大小必须介于指定边界(包括)之间 |
@PositiveOrZero | 被注释的元素必须严格的正数(包含0) |
@Positive | 被注释的元素必须严格的正数(0为无效值) |
@Pattern | 被注释的元素必须符合指定的正则表达式 |
@NotEmpty | 同StringUtils.isNotEmpty |
@NotBlank | 同StringUtils.isNotBlank |
@NegativeOrZero | 带注释的元素必须是一个严格的负数(包含0) |
@Negative | 带注释的元素必须是一个严格的负数(0为无效值) |
@Min | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@Max | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
邮箱规则 | |
@DecimalMax | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@DecimalMin | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@Range(min=1, max=99) | 参数需要在指定的范围内 |
3、校验案例实战
3.1、校验实体
@Data
//@TableName("t_teacher"),实体类名以驼峰的形式命名,可以省略@TableName,否则加上
public class TTeacher implements Serializable {
private static final long serialVersionUID = 1L;
public interface Update{}
public interface Add{}
@TableId(type = IdType.AUTO)
/**
* 主键
*/
@NotNull(groups = {Update.class},message = "不能为空")
@Null(groups = {Add.class},message = "必须为空")
private Integer id;
//教师名称
@Size(min=3, max=20)
private String teacherName;
//教师年龄
private integer age;
//学科
private String course;
//评分
private BigDecimal score;
//教学天数
private Integer teachingDays;
//状态
private String status;
//授课总数
private Integer teachingCount;
//每天教学价格
private BigDecimal priceOfDay;
//开始教学时间
private Date startTime;
//简介
private String description;
//照片
private String photo;
//详细信息
private String detail;
}
3.2、校验Cntroller层实现
/**
* @author dws
* @date 2021年10月29日
*
*/
//如果想在参数中使用 @NotNull 这种注解校验,就必须在类上添加 @Validated
//如果方法中的参数是对象类型,则必须要在参数对象前面添加 @Validated
@Validated
@RestController
@RequestMapping("/teacher")
public class TeacherController {
@Autowired
private TeacherService teacherService;
@IgnoreAnnotation
@GetMapping(value = "/findAll",name = "查询所有老师数据")
public ResponseMessage<List<TTeacher>> findAll(){
return new OverAllExceptionAdvice().sendSuccessResponse(teacherService.findAll());
}
// 这里就声明了要激活Add group对应的校验注解
// 那么就会校验tTeacher的id不能为空
@PostMapping(value = "/add",name = "新增教师")
public ResponseMessage<Void> add(@RequestBody @Validated({TTeacher.Add.class}) TTeacher tTeacher){
teacherService.add(tTeacher);
return new OverAllExceptionAdvice<Void>().sendSuccessResponse();
}
@PutMapping(value = "/update",name = "更新教师信息")
public ResponseMessage<Void> update(@RequestBody TTeacher tTeacher){
teacherService.update(tTeacher);
return new OverAllExceptionAdvice<Void>().sendSuccessResponse();
}
@DeleteMapping(value = "/del/{id}",name = "根据id删除教师信息")
public ResponseMessage<Void> del(@PathVariable("id") @NotNull @Min(value = 10,message = "id不能小于此数字") Integer id){
teacherService.del(id);
return new OverAllExceptionAdvice<Void>().sendSuccessResponse();
}
}
执行结果,http://localhost:8090/elasticsearch/teacher/add
http://localhost:8090/elasticsearch/teacher/del/5
4、统一捕获验证注解异常
/**
* @date:
* @description: 全局统一异常捕获
* ResponseBodyAdvice:在 Controller 执行 return 之后,在 response 返回给客户端之前,执行的对 response 的一些处理,可以实现对 response 数据的一些统一封装或者加密等操作。
*/
@RestControllerAdvice
@Slf4j
public class OverAllExceptionAdvice<T> implements ResponseBodyAdvice<Object> {
public ResponseMessage sendSuccessResponse(){
return new ResponseMessage(ResponseStatus.SUCCESS.getCode(),ResponseStatus.SUCCESS.getMessage(), null);
}
public ResponseMessage<T> sendSuccessResponse(T data) {
return new ResponseMessage(ResponseStatus.SUCCESS.getCode(),ResponseStatus.SUCCESS.getMessage(), data);
}
@ExceptionHandler(BindException.class)
public ResponseMessage sendErrorResponse_Validator(BindException exception){
FieldError err = exception.getFieldError();
String message = "参数{".concat(err.getField()).concat("}").concat(err.getDefaultMessage());
log.info("{} -> {}", err.getObjectName(), message);
return new ResponseMessage(ResponseStatus.UNKNOWEXCEPTION.getCode(),ResponseStatus.UNKNOWEXCEPTION.getMessage(), null);
}
/**
* 统一捕获验证注解异常方法
*/
@ExceptionHandler(ConstraintViolationException.class)
public ResponseMessage sendErrorResponse_ConstraintViolation(ConstraintViolationException exception){
Set<ConstraintViolation<?>> constraintViolations = exception.getConstraintViolations();
for (ConstraintViolation<?> constraintViolation : constraintViolations) {
PathImpl pathImpl = (PathImpl) constraintViolation.getPropertyPath();
// 读取参数字段,constraintViolation.getMessage() 读取验证注解中的message值
String paramName = pathImpl.getLeafNode().getName();
String message = "参数{".concat(paramName).concat("}").concat(constraintViolation.getMessage());
log.info("{} -> {} -> {}", constraintViolation.getRootBeanClass().getName(), pathImpl.toString(), message);
}
return new ResponseMessage(ResponseStatus.UNKNOWEXCEPTION.getCode(),ResponseStatus.UNKNOWEXCEPTION.getMessage(), null);
}
@ExceptionHandler(Exception.class)
public ResponseMessage sendErrorResponse_System(Exception exception){
log.info("请求异常:{}",exception.getMessage());
if (exception instanceof BusinessRuntimeException) {
return this.sendErrorResponse_UserDefined(exception);
}
return new ResponseMessage(ResponseStatus.UNKNOWEXCEPTION.getCode(),ResponseStatus.UNKNOWEXCEPTION.getMessage(), null);
}
/**
* @param methodParameter:
* @param aClass:
* @return: boolean
* @description: 判断是否要执行beforeBodyWrite方法,true为执行,false不执行 —— 通过supports方法,我们可以选择哪些类或哪些方法要对response进行处理,其余的则不处理。
*/
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
//IgnoreAnnotation:被IgnoreAnnotation标记的方法所响应的数据不会走beforeBodyWrite()方法。
if (methodParameter.hasMethodAnnotation(IgnoreAnnotation.class))
return false;
return true;
}
/**
* @param body:
* @param returnType:
* @param selectedContentType:
* @param selectedConverterType:
* @param request:
* @param response:
* @return: java.lang.Object
* @description: 对 response 处理的具体执行方法。
*/
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
ServerHttpResponse response) {
//处理的具体执行方法的逻辑
return body;
}
}
IgnoreAnnotation注解代码实现:
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface IgnoreAnnotation {
}
5、自定义验证异常
代码实现:
/**
* 自定义对教师年龄的验证的注解
*/
@Constraint(validatedBy = {AgeValidator.class})
@Documented
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicty.RUNTIME)
public @interface Age {
String message() default "年龄是非法的,不能超过{max}岁";
int max() default 100;
Class<?>[] groups default {};
Class<? extends Payload>[] payload() default {};
}
/**
* 对教师年龄的验证逻辑实现,当被注释的方法参数小于给出的最大值,即验证通过。
*/
public class AgeValidator implements ConstraintValidator<Age, Integer> {
private Age age;
private Integer max;
public void initialize(Age age) {
this.age = age;
this.max = age.max();
}
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return value < max;
}
}