最近在落地 DDD,希望对 command 进行参数校验,由于部分流量入口是 MQ,所以希望在应用层是用 @Validated 进行参数校验,结果。。。
Controller 中使用 @Validated
@Validated 注解的作用这里就不多做介绍了,具体用法在网上应该有不少。
在之前使用 MVC 架构编码时,通常是将 @Validated 注解或者 @Valid 配置在 Controller 的方法中,如下代码所示:
@PostMapping("common/set")
public Response<?> setCommonSetting(@RequestBody @Validated SetCommonSettingReqVO reqVO) {
//doSomeThings
return Response.success();
}
复制代码
所以在配置应用层校验时,就想当然的按照类似的写法:
public void addClueTrack(@Validated AddClueTrackCommand command) {
//doSomeThings
}
复制代码
结果可想而知,@Validated 注解并不生效。
@Validated 是怎么生效的?
竟然不生效,那么就开始分析原因。
首先可以很容易想到,竟然能在方法执行前就拦截进行校验,那么大概率是使用动态代理。就和 @Transactional 事务注解一样,底层都是基于 AOP 实现动态代理。
接下来为了印证这个想法,就是需要深入看看 Spring 实现的。通过 IDE 可以很方便看到有哪些地方引用了 @Validated 注解:
其中一个类名一下就引起了我的注意 MethodValidationPostProcessor,熟悉 Spring 的小伙伴应该知道,Spring 中有很多 BeanPostProcessor 用于扩展 Bean,Aop 便是基于此实现动态代理的。点进去一看,果不其然:
public class MethodValidationPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor
implements InitializingBean {
private Class<? extends Annotation> validatedAnnotationType = Validated.class;
@Nullable
private Validator validator;
//...
@Override
public void afterPropertiesSet() {
//创建切点
Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);
this.advisor = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice(this.validator));
}
protected Advice createMethodValidationAdvice(@Nullable Validator validator) {
//创建拦截器
return (validator != null ? new MethodValidationInterceptor(validator) : new MethodValidationInterceptor());
}
}
public class AnnotationMatchingPointcut implements Pointcut {
private final ClassFilter classFilter;
private final MethodMatcher methodMatcher;
public AnnotationMatchingPointcut(Class<? extends Annotation> classAnnotationType, boolean checkInherited) {
//切点只针对类级别
this.classFilter = new AnnotationClassFilter(classAnnotationType, checkInherited);
this.methodMatcher = MethodMatcher.TRUE;
}
//...
}
复制代码
MethodValidationPostProcessor 中创建了一个切点,过滤类上添加了 @Validated 的 Bean,只要满足此条件,就会根据 MethodValidationInterceptor 生成对应的代理类。嗯,和 @Transactional 的实现原理差不多。
ok,看到这里我就在应用服务实现上添加了 @Validated 注解,那么此时注解生效了吗?哈哈,进度条还没过半呢😂
理论上类上加上 @Validated 注解,应该会生成动态代理类的,竟然没成功进行参数校验,我能想到的原因有二:
1. MethodValidationPostProcessor 没注入到 BeanFactory 中,所以没生成对应的代理类 2. MethodValidationInterceptor 对还有其他需要满足的条件,而目前还未满足
这里先剧透一下,答案是 2 🌝
MethodValidationInterceptor 需要满足什么条件
竟然答案是2,那这里就先讲一下 MethodValidationInterceptor,MethodValidationPostProcessor 是怎么注册到容器的咱们后面再来讲。
ExecutableValidatorpublic class MethodValidationInterceptor implements MethodInterceptor {
private final Validator validator;
@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
// Standard Bean Validation 1.1 API
ExecutableValidator execVal = this.validator.forExecutables();
Method methodToValidate = invocation.getMethod();
Set<ConstraintViolation<Object>> result;
//获取类本身的实例(非代理