美文网首页
MethodValidationInterceptor执行优先级

MethodValidationInterceptor执行优先级

作者: 码农梦醒 | 来源:发表于2020-04-03 22:52 被阅读0次

    一. 我遇到了什么问题?

    如何让数据验证切面, 在缓存切面之后, 锁切面或事务切面之前执行?

    背景: 我做了一个开源项目, 这个项目以springboot框架为基础, 对其它框架进行了整合. 目的是为了方便我在工作中快速开始一个新项目. 抛开实际业务, 我们在项目中常用到的功能有哪些呢? 答案是: 缓存, 数据验证, 锁, 事务.

    当执行一个业务方法我期望是怎样执行呢?

    1. 先从缓存中查询是否有可用的缓存结果, 如果有, 则直接返回, 如果没有则进入下一步.
    2. 对入参进行数据校验. 校验失败, 则直接抛出异常, 校验通过, 则进入下一步.
    3. 如果需要保证唯一性或同步, 此时需要使用分布式锁. 如果不需要则进入下一步
    4. 如果需要事务, 则开启事务
    5. =========> 执行真正的业务方法
    6. 如果存在事务则提交或回滚事务
    7. 如果存在锁,则释放分布式锁
    

    我期望除了第五步之外的所有步骤, 对于普通的开发者而言都要是透明的, 不用关注具体如何实现, 只需要按我规定的方式编写代码即可. 那么如何实现? 答案肯定是使用代理设计模式. 而spring要实现代理模式, 原则上来说是很容易的, 并且缓存, 数据验证, 锁, 事务这些东西, spring都以AOP方式进行了实现. 那么我还会有什么问题呢? 没错, 就是执行顺序问题. 直白的说就是各个切面的执行顺序问题. 我需要依次执行缓存切面, 数据验证切面, 锁切面, 事务切面.

    二. 我是如何解决的

    关于自定义切面指定执行优先级

    稍微到网上搜索一下, 我们能很轻松的了解到, 如果想指定切面的执行优先级, 那么有两种方式:

    • 使用@Order注解
    • 实现org.springframework.core.Ordered接口

    缓存切面指定执行优先级

    @EnableCaching(order = GlobalConstant.AOP_ORDER_CACHE)
    

    事务切面指定执行优先级

    @EnableTransactionManagement(order = GlobalConstant.AOP_ORDER_TRANSACTIONAL)
    

    数据验证切面指定执行优先级

    package org.pzy.opensource.redis.support.springboot.aop;
    
    import org.springframework.aop.Advisor;
    import org.springframework.aop.framework.Advised;
    import org.springframework.aop.framework.AopInfrastructureBean;
    import org.springframework.aop.framework.ProxyFactory;
    import org.springframework.aop.support.AopUtils;
    import org.springframework.aop.support.DefaultPointcutAdvisor;
    import org.springframework.core.Ordered;
    import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
    
    /**
     * 自定义`MethodValidationPostProcessor`类, 覆盖`postProcessAfterInitialization`解决`MethodValidationInterceptor`执行优先级问题
     * @author pan
     * @date 2020/3/30
     */
    public class WinterMethodValidationPostProcessor extends MethodValidationPostProcessor {
        @Override
        public void afterPropertiesSet() {
            super.afterPropertiesSet();
            DefaultPointcutAdvisor defaultPointcutAdvisor = (DefaultPointcutAdvisor) this.advisor;
            defaultPointcutAdvisor.setOrder(this.getOrder());
        }
    
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) {
            // 如果是AOP相关的基础组件bean,如ProxyProcessorSupport类及其子类,则直接返回
            if (this.advisor == null || bean instanceof AopInfrastructureBean) {
                // Ignore AOP infrastructure such as scoped proxies.
                return bean;
            }
    
            if (bean instanceof Advised) {
                // 如果已经是Advised的,即已经是被动态代理的实例,则直接添加advisor
                Advised advised = (Advised) bean;
                if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) {
                    // 如果没有被frozen(即冷冻,不再做改动的动态代理实例)且是Eligbile(合适的),则把其添加到advisor中。根据配置决定插入位置
                    // Add our local Advisor to the existing proxy's Advisor chain...
                    if (this.beforeExistingAdvisors) {
                        advised.addAdvisor(0, this.advisor);
                    } else {
                        // 获取已有切面
                        Advisor[] advisorArr = advised.getAdvisors();
                        // 遍历已有切面与当前切面的order值比较, 找到第一个比当前切面order值大的切面, 并记下该位置
                        Integer curAdvisorPos = null;
                        for (int i = 0; i < advisorArr.length; i++) {
                            Advisor tmp = advisorArr[i];
                            if (tmp instanceof Ordered) {
                                int tmpOrder = ((Ordered) tmp).getOrder();
                                if (tmpOrder > this.getOrder()) {
                                    // 当前拦截器的执行优先级高于数组中当前循环的这个优先级, 所以在这个位置插入当前拦截器
                                    curAdvisorPos = i;
                                    break;
                                }
                            }
                        }
                        if (null == curAdvisorPos) {
                            advised.addAdvisor(this.advisor);
                        } else {
                            // 在第一个比当前切面order值大位置插入当前切面, 其它切面一次往后移一位
                            advised.addAdvisor(curAdvisorPos, this.advisor);
                        }
                    }
                    return bean;
                }
            }
    
            if (isEligible(bean, beanName)) {
                // 如果是Eligible合适的,且还不是被代理的类,则创建一个代理类的实例并返回
                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;
        }
    }
    

    三. 解决问题之后的总结

    MethodValidationPostProcessor, MethodValidationInterceptor是做什么的?

    MethodValidationPostProcessor 继承关系

    image.png

    MethodValidationInterceptor 继承关系

    image.png

    我是如何一步一步解决这个问题的?

    首先无脑搜索了一番

    百度, 谷歌, 必应... 各种关键词, 英文的, 中文的, 慢慢查. 我觉得应该也有人会有类似的问题. 确实有类似问题, 但不是说使用@Order注解就是实现Ordered接口. 或者一些答非所问. 情况似乎陷入了僵局. 不死心, 又搜了几次, 还是和之前一样, 没有进展, 然后隔了几天, 还是不死心, 又搜了几次, 还是失败.

    这似乎和bean实例化有关

    于是搜索bean实例化过程. 期望会有直接的答案或发现一些有启发性的文章. 很好, 完全没有搜到有用的信息. 大多是一些大大们相互借鉴的文章. 可是不甘心啊, 于是我找了点关于spring原理的视频. 下完视频用了2-3天, 硬着头皮听了2天, 很好, 视频里就是带着我们以debug方式看源码, 但我感觉他主要还是让我们背源代码, 去背类名, 方法名. 唯一有点意义的可能只有一句话: spring的套路是把真正重要的逻辑都放到doXxx方法中, 看源码先打断点, 看返回值在哪个地方产生了变化, 那个让返回值变化的地方, 可能就是需要重点关注的地方, 情况似乎又陷入了僵局. 可是, 我觉得我的方向应该是没错的.

    无意间发现原来spring的代理对象结构是这样的

    image.png
    image.png

    并且参数校验切面也在里面, 那么我是不是可以大胆假设这个advisors, 这里面存放了该bean实例所有的切面, 并且存放顺序就是这些切面的执行顺序.

    小心求证: 在自定义切面中加断点, 在方法验证切面中加断点, 发现他们的执行顺序确实和这里的存放顺序一致, 那么我是不是可以再次大胆假设, 只要我解决了advisors数据的存放顺序问题, 就能解决参数验证切面的执行顺序问题?

    advisors这个属性的属性值是什么时候被填充的?

    1. 首先要解决的是, spring的源码是在哪个地方创建出了代理对象?
    2. 然后才是advisors这个属性的属性值是什么时候被填充的?

    关于第一点, 我知道对象的创建肯定需要调用构造方法, 即使是spring要创建对象, 也需要调用构造方法, 于是我就在在无参构造方法中打了个断点, 然后观察Frames里的内容

    image.png

    找spring包的方法, 并回想起视频里说的, spring源码套路是将真正重要的逻辑都放到doXxx方法中, 并根据方法名, 锁定到了doCreateBean方法

    image.png image.png

    通过断点我发现559行获取到了原始对象

    doCreate方法节选

    protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
                throws BeanCreationException {
    
            // Instantiate the bean.
            BeanWrapper instanceWrapper = null;
            if (mbd.isSingleton()) {
                instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
            }
            if (instanceWrapper == null) {
                            // 重点: 创建bean的包装对象, 里面包含真正的bean实例
                instanceWrapper = createBeanInstance(beanName, mbd, args);
            }
                    // 重点: 获取真正的bean实例
            final Object bean = instanceWrapper.getWrappedInstance();
            Class<?> beanType = instanceWrapper.getWrappedClass();
            if (beanType != NullBean.class) {
                mbd.resolvedTargetType = beanType;
            }
    
            // Allow post-processors to modify the merged bean definition.
            synchronized (mbd.postProcessingLock) {
                if (!mbd.postProcessed) {
                    try {
                        applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
                    }
                    catch (Throwable ex) {
                        throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                "Post-processing of merged bean definition failed", ex);
                    }
                    mbd.postProcessed = true;
                }
            }
    
            // Eagerly cache singletons to be able to resolve circular references
            // even when triggered by lifecycle interfaces like BeanFactoryAware.
            boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                    isSingletonCurrentlyInCreation(beanName));
            if (earlySingletonExposure) {
                if (logger.isTraceEnabled()) {
                    logger.trace("Eagerly caching bean '" + beanName +
                            "' to allow for resolving potential circular references");
                }
                addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
            }
    
            // Initialize the bean instance.
            Object exposedObject = bean;
            try {
                populateBean(beanName, mbd, instanceWrapper);
                            // 执行完下面这句代码之后, 我发现, bean实例发生了变化, 不再是真实的bean实例对象, 而是转换成了代理对象, 就是我最开始截图的那个结构, 并且已经填充好了`advisors`值, 于是我感觉这里应该就是转折点了, 继续跟进
                exposedObject = initializeBean(beanName, exposedObject, mbd);
            }
            catch (Throwable ex) {
                if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
                    throw (BeanCreationException) ex;
                }
                else {
                    throw new BeanCreationException(
                            mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
                }
            }
    
            if (earlySingletonExposure) {
                Object earlySingletonReference = getSingleton(beanName, false);
                if (earlySingletonReference != null) {
                    if (exposedObject == bean) {
                        exposedObject = earlySingletonReference;
                    }
                    else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
                        String[] dependentBeans = getDependentBeans(beanName);
                        Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
                        for (String dependentBean : dependentBeans) {
                            if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                                actualDependentBeans.add(dependentBean);
                            }
                        }
                        if (!actualDependentBeans.isEmpty()) {
                            throw new BeanCurrentlyInCreationException(beanName,
                                    "Bean with name '" + beanName + "' has been injected into other beans [" +
                                    StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                                    "] in its raw version as part of a circular reference, but has eventually been " +
                                    "wrapped. This means that said other beans do not use the final version of the " +
                                    "bean. This is often the result of over-eager type matching - consider using " +
                                    "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
                        }
                    }
                }
            }
    
            // Register bean as disposable.
            try {
                registerDisposableBeanIfNecessary(beanName, bean, mbd);
            }
            catch (BeanDefinitionValidationException ex) {
                throw new BeanCreationException(
                        mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
            }
    
            return exposedObject;
        }
    
    protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
            if (System.getSecurityManager() != null) {
                AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
                    invokeAwareMethods(beanName, bean);
                    return null;
                }, getAccessControlContext());
            }
            else {
                invokeAwareMethods(beanName, bean);
            }
    
            Object wrappedBean = bean;
            if (mbd == null || !mbd.isSynthetic()) {
                            // 这里执行完之后, bean对象从直接对象, 变成了代理对象, 继续跟进
                wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
            }
    
            try {
                invokeInitMethods(beanName, wrappedBean, mbd);
            }
            catch (Throwable ex) {
                throw new BeanCreationException(
                        (mbd != null ? mbd.getResourceDescription() : null),
                        beanName, "Invocation of init method failed", ex);
            }
            if (mbd == null || !mbd.isSynthetic()) {
                wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
            }
    
            return wrappedBean;
        }
    
    @Override
        public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
                throws BeansException {
    
            Object result = existingBean;
            for (BeanPostProcessor processor : getBeanPostProcessors()) {
                             // 此处使用idea的条件断点, 终于在processor为WinterMethodValidationPostProcessor类的对象的时候, 停了下来, 于是我跟进代码, 终于发现方法验证切面就是在WinterMethodValidationPostProcessor实例的postProcessAfterInitialization方法中进行填充的, 遂改变原始逻辑, 加入排序逻辑, 问题最终得以解决.
                Object current = processor.postProcessBeforeInitialization(result, beanName);
                if (current == null) {
                    return result;
                }
                result = current;
            }
            return result;
        }
    

    后记

    不容易啊, 我的坚持与执着这一次终于有了一个好的结果. 我感觉以后分析类似的问题, 我都有思路了. 不至于像以前一样盲目搜索, 盲目提问了. 这应该是个好的开始. 开心! 开心! 为防忘记, 遂有此记录!!

    相关文章

      网友评论

          本文标题:MethodValidationInterceptor执行优先级

          本文链接:https://www.haomeiwen.com/subject/ebqlphtx.html