【Spring Boot Validation】切面 MethodValidationInterceptor 为什么总在最后执行

76 阅读3分钟

前言

语句描述不是专业,比如统一用【切面】代指各个aop处理的代码,大家意会即可

问题引入

你是否有注意过各个注解下切面的执行顺序,假如有有以下代码


@Async(xxxx)
@Cacheable(xxxx)
@Transactional(xxxx)
@CustomBizLog(xxx)
@RedisLock(xxx)
public Future<Data> queryData(@Valid Query query) {
    // xxxxxxxxxx
}

从代码中至少可以看出,包含了 异步、缓存、事务、参数校验、两个自定义注解(业务日志、redis分布式锁)等至少6个切面,此时将不得不考虑它们的执行顺序


如何查看执行顺序

org.springframework.aop.framework.CglibAopProxy.DynamicAdvisedInterceptor#intercept

在这个方法中定位到这一行

List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

断点打下面即可看到 chain 里面的执行顺序

假设我的自定义切面 @CustomBizLog 配置了 order=10, @RedisLock 的 order=0,那大概率顺序是这样:

  • AnnotationAsyncExecutionInterceptor
  • ExposeInvocationInterceptoe
  • AspectJAroundAdvice (aspectName=redisLockAspect)
  • AspectJAroundAdvice (aspectName=customBizLogAspect)
  • CacheInterceptor
  • TransactionInterceptor
  • MethodValidationInterceptor

调整顺序

业务上的查询,实际还需要再多张表中记录和变更一些数据的,所以需要用事务控制

整体顺序需要是:

  1. 尝试从缓存获取,获取到则返回
  2. 缓存没有,需要先加锁,然后进入事务管理,进入日志记录切面,在进入业务代码

于是在调整事务和缓存注解上调整顺序

@EnableTransactionManagement(order = 1)
@EnableCaching(order = -1)

然后发现需要将参数校验也提前到缓存切面之前,但蒙蔽了,没有找到可配置的方式


先说结论,处理方法1

把 bean 代理成执行 MethodValidationInterceptorAnnotationAsyncExecutionInterceptor 的 PostProcessor 不是 AnnotationAwareAspectJAutoProxyCreator,即便各种方式给 advisor 设置了排序,最后也无法同其它的几个切面排序。个人简单看了下源码,常规方法只能给加到几个切面的前后,大佬有办法望告知


可以在 AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsAfterInitialization 中断点,或者跟随注解或auto configuration类查找,定位到它们分别由 MethodValidationPostProcessorAsyncAnnotationBeanPostProcessor 来包装代理

最后执行到 AbstractAdvisingBeanPostProcessor#postProcessAfterInitialization 进行代理,可以发现 advisor(即 pointcut + advice(MethodValidationInterceptor)) 只能加到最前或最后

public Object postProcessAfterInitialization(Object bean, String beanName) {
    if (this.advisor == null || bean instanceof AopInfrastructureBean) {
       return bean;
    }

    if (bean instanceof Advised) {
       Advised advised = (Advised) bean;
       if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) {
          // Add our local Advisor to the existing proxy's Advisor chain...
          if (this.beforeExistingAdvisors) {
             advised.addAdvisor(0, this.advisor);
          }
          else {
             advised.addAdvisor(this.advisor);
          }
          return bean;
       }
    }

    if (isEligible(bean, beanName)) {
       ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);
       if (!proxyFactory.isProxyTargetClass()) {
          evaluateProxyInterfaces(bean.getClass(), proxyFactory);
       }
       proxyFactory.addAdvisor(this.advisor);
       customizeProxyFactory(proxyFactory);
       return proxyFactory.getProxy(getProxyClassLoader());
    }

    // No proxy needed.
    return bean;
}

所以最后配置代码为(不同spring版本自行参考 ValidationAutoConfiguration 中的写法)

    @Bean
    public static MethodValidationPostProcessor methodValidationPostProcessor(Environment environment,
                                                                              ObjectProvider<Validator> validator, ObjectProvider<MethodValidationExcludeFilter> excludeFilters) {
        FilteredMethodValidationPostProcessor processor = new FilteredMethodValidationPostProcessor(
                excludeFilters.orderedStream());
        boolean proxyTargetClass = environment.getProperty("spring.aop.proxy-target-class", Boolean.class, true);
        processor.setProxyTargetClass(proxyTargetClass);
        processor.setValidatorProvider(validator);
        // 加上以下2段代码
        processor.setBeforeExistingAdvisors(true);
        processor.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);
        return processor;
    }

继续分析

我试过把上述 processor 的 order 设置到 HIGHEST_PRECEDENCE,结果 chain 没有 MethodValidationInterceptor 了,怎么回事?只能看 AnnotationAwareAspectJAutoProxyCreator 如何 proxy bean 的。

定位到对应 AbstractAutoProxyCreator#postProcessAfterInitialization,发现调用 wrapIfNecessary,里面有段关键代码

// 获取当前bean适用的advisor,内部排序
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
    this.advisedBeans.put(cacheKey, Boolean.TRUE);
    // 关键,这里根据 bean 的 class 重新创建生成了个 bean,原本代理 bean 里的 advisor 丢失了
    Object proxy = createProxy(
          bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
    this.proxyTypes.put(cacheKey, proxy.getClass());
    return proxy;
}

处理方法2

笔者能力有限,只做得到其中一种解决方案,就是继承 MethodValidationPostProcessor, 重写 postProcessAfterInitialization 方法,对 advisor 进行排序

public class MyMethodValidationPostProcessor extends FilteredMethodValidationPostProcessor {

    private int advisorOrder = 0;
    
    public MyMethodValidationPostProcessor(Stream<? extends MethodValidationExcludeFilter> excludeFilters) {
        super(excludeFilters);
    }

    public MyMethodValidationPostProcessor(Collection<? extends MethodValidationExcludeFilter> excludeFilters) {
        super(excludeFilters);
    }

    public void setAdvisorOrder(int advisorOrder) {
        this.advisorOrder = advisorOrder;
    }

    @Override
    public void afterPropertiesSet() {
        super.afterPropertiesSet();
        DefaultPointcutAdvisor advisor = (DefaultPointcutAdvisor) this.advisor;
        advisor.setOrder(this.advisorOrder);
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        if (this.advisor == null || bean instanceof AopInfrastructureBean) {
            return bean;
        }

        if (bean instanceof Advised advised) {
            if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) {
                if (advised.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE &&
                        advised.getAdvisorCount() > 0) {
                    advised.addAdvisor(advised.getAdvisorCount() - 1, this.advisor);
                }
                else {
                    advised.addAdvisor(this.advisor);
                }
                // 这里进行排序
                Advisor[] advisors = advised.getAdvisors();
                AnnotationAwareOrderComparator.sort(advisors);
                for (Advisor value : advisors) {
                    advised.removeAdvisor(value);
                }
                for (Advisor value : advisors) {
                    advised.addAdvisor(value);
                }
                return bean;
            }
        }

        // ........
    }
}
@Bean
public static MethodValidationPostProcessor methodValidationPostProcessor(Environment environment,
                                                                          ObjectProvider<Validator> validator, ObjectProvider<MethodValidationExcludeFilter> excludeFilters) {
    MyMethodValidationPostProcessor processor = new MyMethodValidationPostProcessor(
            excludeFilters.orderedStream());
    // 设置排序        
    processor.setAdvisorOrder(-2);
    boolean proxyTargetClass = environment.getProperty("spring.aop.proxy-target-class", Boolean.class, true);
    processor.setProxyTargetClass(proxyTargetClass);
    processor.setValidatorProvider(validator);
    return processor;
}