@Valid的实现原理,他是怎么解析的?

### requestBody @Valid参数校验实现原理 在spring-mvc中,RequestResponseBodyMethodProcessor是用于解析@RequestBody标注的参数以及处理@ResponseBody标注方法的返回值的。显然,执行参数校验的逻辑肯定就在解析参数的方法resolveArgument()中: ```java public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor { @Override public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { parameter = parameter.nestedIfOptional(); //将请求数据封装到DTO对象中 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); 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); } } ``` 可以看到,resolveArgument()调用了validateIfApplicable()进行参数校验。 ```java protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) { // 获取参数注解,比如@RequestBody、@Valid、@Validated Annotation[] annotations = parameter.getParameterAnnotations(); for (Annotation ann : annotations) { // 先尝试获取@Validated注解 Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class); //如果直接标注了@Validated,那么直接开启校验。 //如果没有,那么判断参数前是否有Valid起头的注解。 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; } } } ``` 看到这里,大家应该能明白为什么这种场景下@Validated、@Valid两个注解可以混用。我们接下来继续看WebDataBinder.validate()实现。 ```java @Override public void validate(Object target, Errors errors, Object... validationHints) { if (this.targetValidator != null) { processConstraintViolations( //此处调用Hibernate Validator执行真正的校验 this.targetValidator.validate(target, asValidationGroups(validationHints)), errors); } } ``` 最终发现底层最终还是调用了Hibernate Validator进行真正的校验处理。 ### 方法级别的参数校验实现原理 将参数一个个平铺到方法参数中,然后在每个参数前面声明约束注解的校验方式,就是方法级别的参数校验。实际上,这种方式可用于任何Spring Bean的方法上,比如Controller/Service等。其底层实现原理就是AOP,具体来说是通过MethodValidationPostProcessor动态注册AOP切面,然后使用MethodValidationInterceptor对切点方法织入增强。 ```java public class MethodValidationPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessorimplements InitializingBean { @Override public void afterPropertiesSet() { //为所有`@Validated`标注的Bean创建切面 Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true); //创建Advisor进行增强 this.advisor = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice(this.validator)); } //创建Advice,本质就是一个方法拦截器 protected Advice createMethodValidationAdvice(@Nullable Validator validator) { return (validator != null ? new MethodValidationInterceptor(validator) : new MethodValidationInterceptor()); } } ``` 接着看一下MethodValidationInterceptor: ```java public class MethodValidationInterceptor implements MethodInterceptor { @Override public Object invoke(MethodInvocation invocation) throws Throwable { //无需增强的方法,直接跳过 if (isFactoryBeanMetadataMethod(invocation.getMethod())) { return invocation.proceed(); } //获取分组信息 Class<?>[] groups = determineValidationGroups(invocation); ExecutableValidator execVal = this.validator.forExecutables(); Method methodToValidate = invocation.getMethod(); Set<ConstraintViolation<Object>> result; try { //方法入参校验,最终还是委托给Hibernate Validator来校验 result = execVal.validateParameters( invocation.getThis(), methodToValidate, invocation.getArguments(), groups); } catch (IllegalArgumentException ex) { ... } //有异常直接抛出 if (!result.isEmpty()) { throw new ConstraintViolationException(result); } //真正的方法调用 Object returnValue = invocation.proceed(); //对返回值做校验,最终还是委托给Hibernate Validator来校验 result = execVal.validateReturnValue(invocation.getThis(), methodToValidate, returnValue, groups); //有异常直接抛出 if (!result.isEmpty()) { throw new ConstraintViolationException(result); } return returnValue; } } ``` 实际上,不管是requestBody参数校验还是方法级别的校验,最终都是调用Hibernate Validator执行校验,Spring Validation只是做了一层封装。