本篇主要讲解使用javax.validation.constraints,org.hibernate.validator.constraints下的校验方法对实体类进行自动校验, 直接对数据进行校验,通过对接收的数据进行校验,如果不符合我们定义的要求则会提示对应的message信息,具体怎么做的,下面一步步来说明。
初步介绍校验
先看下javax.validation.constraints下面的一些数据校验方法:
AssertFalse
AssertTrue
DecimalMax
DecimalMin
Digits
Email
Future
FutureOrPresent
Min
Max
Negative
NegativeOrZero
NotBlank
NotEmpty
NotNull
Null
Past
PastOrPresent
Pattern
Positive
PositiveOrZero
Size
再来看下org.hibernate.validator.constraints部分校验方法
Length
URL
Range
SafeHtml
具体怎么使用不是本节的重点,大家可以在需要用到的时候参考源码获取网上查阅资料,下面据两个例子来讲解:
@Length(min = 1, max = 30, message = "规则描述字段长度需要在{min}和{max}之间", groups = {InsertGroup.class, UpdateGroup.class})
private String ruleDesc;
@ApiModelProperty("规则优先级")
@NotEmpty(message="规则优先级不可为空",groups = {InsertGroup.class,UpdateGroup.class})
@Pattern(regexp = RegexpUtils.NON_ZERO_NEGATIVE_INTEGERS_REGEXP,message = "规则优先级必须为正整数")
private String salience;
使用上面的校验需要引入依赖:
spring-boot-starter-web包里面有hibernate-validator包,不需要引用hibernate validator依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
如果是Spring Mvc,那可以直接添加hibernate-validator依赖
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>7.0.1.Final</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
javax.validation在spring-boot-starter-web包也已经被引入了
校验分组
如果需要分组,可以定义分组:
public interface UpdateGroup extends Update {
}
import org.apache.ibatis.annotations.Insert;
public interface InsertGroup extends Insert {
}
hibernate的校验模式
Hibernate Validator有以下两种验证模式:
1 、普通模式(默认是这个模式)
普通模式(会校验完所有的属性,然后返回所有的验证失败信息)
2、快速失败返回模式
快速失败返回模式(只要有一个验证失败,则返回)
默认是普通模式
配置校验模式:
@Configuration
public class ValidatorConfiguration {
@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
.addProperty("hibernate.validator.fail_fast", "true")
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
return validator;
}
}
rest接口参数校验
对@PathVariable和@RequestParam参数进行校验,需要在入参声明约束的注释
@GetMapping
public Result getByUri(@RequestParam @NotEmpty String uri) {
List<GatewayRoutePO> list = gatewayRoutService.query(GatewayRouteQueryParam.builder().uri(uri).build());
return Result.success(Optional.ofNullable(list).get().stream().findFirst());
}
@GetMapping(value = "/get/{id}")
public Result get(@PathVariable(name = "id") @NotBlank String routId) {
LOGGER.info("get with id:{}", routId);
return Result.success(gatewayRoutService.getRouteByRouteId(routId));
}
对于RequestBody的参数校验直接在实体参数前加上 Validated 或者javax.validation.Valid
@PostMapping(value = "/conditions")
public Result search(@Validated @RequestBody GatewayRouteQueryParam gatewayRouteQueryParam) {
return Result.success(gatewayRoutService.query(gatewayRouteQueryParam));
}
校验原理
这里需要提到RequestResponseBodyMethodProcessor,这个类有两个作用,
1:用于解析@RequestBody标注的参数
2:处理ResponseBody标注方法的返回值
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(RequestBody.class);
}
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
returnType.hasMethodAnnotation(ResponseBody.class));
}
解析RequestBody标注参数的方法是resolveArgument
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
parameter = parameter.nestedIfOptional();
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
String name = Conventions.getVariableNameForParameter(parameter);
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
if (arg != null) {
//进行参数校验
validateIfApplicable(binder, parameter);
//校验不通过抛MethodArgumentNotValidException异常
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
}
if (mavContainer != null) {
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
}
}
return adaptArgumentIfNecessary(arg, parameter);
}
校验方法:AbstractMessageConverterMethodArgumentResolver#validateIfApplicable 获取Validated注解,根据注解参数和注解信息验证DataBinder#validate
protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
Annotation[] annotations = parameter.getParameterAnnotations();
for (Annotation ann : annotations) {
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
binder.validate(validationHints);
break;
}
}
}
public void validate(Object... validationHints) {
Object target = getTarget();
Assert.state(target != null, "No target to validate");
BindingResult bindingResult = getBindingResult();
// Call each validator with the same binding result
for (Validator validator : getValidators()) {
if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator) {
((SmartValidator) validator).validate(target, bindingResult, validationHints);
}
else if (validator != null) {
validator.validate(target, bindingResult);
}
}
}
通过在参数上加注释校验参数是否符合条件, 实际上就是使用AOP拦截,Spring通过MethodValidationPostProcessor动态主从AOP切面,然后使用MethodValidationInterceptor对切点进行织入增强
public class MethodValidationPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor
implements InitializingBean {
@Override
public void afterPropertiesSet() {
//创建切面
Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);
//创建advisor进行增强
this.advisor = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice(this.validator));
}
}
自定义校验:
定义自定义约束,有三个步骤
自定义约束注解:
@Documented
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Constraint(validatedBy = {MobileValidator.class})
@Retention(RUNTIME)
@Repeatable(Mobile.List.class)
public @interface Mobile {
/**
* 错误提示信息,可以写死,也可以填写国际化的key
*/
String message() default "手机号码不正确";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String regexp() default "^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\\d{8}$";
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Documented
@interface List {
Mobile[] value();
}
}
public class MobileValidator implements ConstraintValidator<Mobile, String> {
/**
* 手机验证规则
*/
private Pattern pattern;
@Override
public void initialize(Mobile mobile) {
pattern = Pattern.compile(mobile.regexp());
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
return pattern.matcher(value).matches();
}
}
测试自定义校验:
import org.springframework.util.CollectionUtils;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.util.Iterator;
import java.util.Set;
public class ValidatorUtil {
private static final Validator validator= Validation.buildDefaultValidatorFactory().getValidator();
public static <T> String validate(T object){
Set<ConstraintViolation<T>> violationSet=validator.validate(object);
if(violationSet!=null&&!CollectionUtils.isEmpty(violationSet)){
Iterator<ConstraintViolation<T>> iterator=violationSet.iterator();
StringBuffer sb=new StringBuffer(64);
while(iterator.hasNext()){
sb.append(iterator.next().getMessage()).append(";");
}
return sb.toString();
}
return null;
}
}
配置全局校验
/**
* 全局参数验证
*/
@Slf4j
@Order(1)
@ControllerAdvice
@RestController
public class ValidationException {
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public CommonResult ValidExceptionHandler(MethodArgumentNotValidException exception) throws Exception {
BindingResult bindingResult = exception.getBindingResult();
if (bindingResult.hasErrors()) {
String defaultMessage = bindingResult.getFieldError().getDefaultMessage();
return CommonResult.failed(defaultMessage);
}
return CommonResult.success("校验通过");
}
}
在接口中使用数据校验:
@PostMapping("/save")
@ApiOperation(value = "保存规则数据")
public CommonResult saveCvbRule(@Validated(InsertGroup.class) @RequestBody Rule rule)
如果校验不通过会向前端返回message校验失败信息
参考资料:基于springboot使用hibernate validator校验数据
hibernate-validation官网