spring method validation实现原理研究
一言以蔽之:
spring validation方法级别校验的实现是通过MethodValidationPostProcessor类动态注册AOP切面,使用MethodValidationInterceptor对切点方法织入增强。
注意:学习本文必须要掌握:AOP的基本概念、拦截器的功能和原理、spring aware接口的功能和大致原理。
如果对以上知识的不太清楚的话,建议首先研究下这些。
具体实现流程如下所示:
1、 如下图所示,在MethodValidationPostProcessor的afterPropertiesSet方法中,动态创建了一个AnnotationMatchingPointcut类型切点,同时调用createMethodValidationAdvice方法创建出对应的增强。
注意:afterPropertiesSet方法是InitializingBean接口中定义的方法,用于在spring容器初始化的时候,进行对Bean的一些初始化操作,调用的时机是在bean的属性都set完毕。
AnnotationMatchingPointcut,见文知意,即注解匹配类型的切点,MethodValidationPostProcessor会创建一个这样的切点。如下图,其中classAnnotationType接收到的值Validated.class,表明该切点匹配所有标注了Validated注解的类,同时checkInherited参数是true,表明即使子类不标注Validated注解,但是其父类或者接口标注之后,那么子类也会命中切点。
创建完切点之后,会创建增强,创建过程如下所示:
其中MethodValidationInterceptor是aop联盟MethodInterceptor接口的实现,MethodInterceptor接口中声明了invoke方法,在该方法实现中,可以织入对应的增强。
@FunctionalInterface
public interface MethodInterceptor extends Interceptor {
/**
* Implement this method to perform extra treatments before and
* after the invocation. Polite implementations would certainly
* like to invoke {@link Joinpoint#proceed()}.
* @param invocation the method invocation joinpoint
* @return the result of the call to {@link Joinpoint#proceed()};
* might be intercepted by the interceptor
* @throws Throwable if the interceptors or the target object
* throws an exception
*/
Object invoke(MethodInvocation invocation) throws Throwable;
}
其中MethodInterceptor织入增强的代码如下所示:
在这里调用Bean validation的检验接口
1、进行方法参数的校验
execVal.validateParameters(invocation.getThis(),
methodToValidate, invocation.getArguments(), groups);
2、进行方法返回值的校验
execVal.validateReturnValue(invocation.getThis(),
methodToValidate, returnValue, groups);
3、如果存在违反约束的情况,将会抛出ConstraintViolationException异常,我们可以使用ExceptionHandler显示捕获该异常,然后返回前端对应的message。
if (!result.isEmpty()) {
throw new ConstraintViolationException(result);
}
捕获异常代码可以参考如下:
@ControllerAdvice
public class GlobalExceptionHandler {
@ResponseBody
@ExceptionHandler
public String handleException(Throwable e) {
//方法级别校验异常,注意笔者这里使用快速失败机制,如果存在一条违反的约定,那么就不会继续后续的校验,所以这里返回的ConstraintViolations只有一条
//读者可以根据自己的需求,按需返回对应的错误信息
if (e instanceof ConstraintViolationException) {
return ((ConstraintViolationException) e).getConstraintViolations().iterator().next().getMessage();
}
//对象级别校验,绑定异常
if (e instanceof BindException) {
return e.getMessage();
}
return e.getMessage();
}
}