MethodValidationInterceptor執(zhí)行優(yōu)先級問題

一. 我遇到了什么問題?

如何讓數(shù)據(jù)驗證切面, 在緩存切面之后, 鎖切面或事務(wù)切面之前執(zhí)行?

背景: 我做了一個開源項目, 這個項目以springboot框架為基礎(chǔ), 對其它框架進行了整合. 目的是為了方便我在工作中快速開始一個新項目. 拋開實際業(yè)務(wù), 我們在項目中常用到的功能有哪些呢? 答案是: 緩存, 數(shù)據(jù)驗證, 鎖, 事務(wù).

當(dāng)執(zhí)行一個業(yè)務(wù)方法我期望是怎樣執(zhí)行呢?

1. 先從緩存中查詢是否有可用的緩存結(jié)果, 如果有, 則直接返回, 如果沒有則進入下一步.
2. 對入?yún)⑦M行數(shù)據(jù)校驗. 校驗失敗, 則直接拋出異常, 校驗通過, 則進入下一步.
3. 如果需要保證唯一性或同步, 此時需要使用分布式鎖. 如果不需要則進入下一步
4. 如果需要事務(wù), 則開啟事務(wù)
5. =========> 執(zhí)行真正的業(yè)務(wù)方法
6. 如果存在事務(wù)則提交或回滾事務(wù)
7. 如果存在鎖,則釋放分布式鎖

我期望除了第五步之外的所有步驟, 對于普通的開發(fā)者而言都要是透明的, 不用關(guān)注具體如何實現(xiàn), 只需要按我規(guī)定的方式編寫代碼即可. 那么如何實現(xiàn)? 答案肯定是使用代理設(shè)計模式. 而spring要實現(xiàn)代理模式, 原則上來說是很容易的, 并且緩存, 數(shù)據(jù)驗證, 鎖, 事務(wù)這些東西, spring都以AOP方式進行了實現(xiàn). 那么我還會有什么問題呢? 沒錯, 就是執(zhí)行順序問題. 直白的說就是各個切面的執(zhí)行順序問題. 我需要依次執(zhí)行緩存切面, 數(shù)據(jù)驗證切面, 鎖切面, 事務(wù)切面.

二. 我是如何解決的

關(guān)于自定義切面指定執(zhí)行優(yōu)先級

稍微到網(wǎng)上搜索一下, 我們能很輕松的了解到, 如果想指定切面的執(zhí)行優(yōu)先級, 那么有兩種方式:

  • 使用@Order注解
  • 實現(xiàn)org.springframework.core.Ordered接口

緩存切面指定執(zhí)行優(yōu)先級

@EnableCaching(order = GlobalConstant.AOP_ORDER_CACHE)

事務(wù)切面指定執(zhí)行優(yōu)先級

@EnableTransactionManagement(order = GlobalConstant.AOP_ORDER_TRANSACTIONAL)

數(shù)據(jù)驗證切面指定執(zhí)行優(yōu)先級

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`執(zhí)行優(yōu)先級問題
 * @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相關(guān)的基礎(chǔ)組件bean,如ProxyProcessorSupport類及其子類踱讨,則直接返回
        if (this.advisor == null || bean instanceof AopInfrastructureBean) {
            // Ignore AOP infrastructure such as scoped proxies.
            return bean;
        }

        if (bean instanceof Advised) {
            // 如果已經(jīng)是Advised的走哺,即已經(jīng)是被動態(tài)代理的實例勿侯,則直接添加advisor
            Advised advised = (Advised) bean;
            if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) {
                // 如果沒有被frozen(即冷凍,不再做改動的動態(tài)代理實例)且是Eligbile(合適的)阱飘,則把其添加到advisor中。根據(jù)配置決定插入位置
                // Add our local Advisor to the existing proxy's Advisor chain...
                if (this.beforeExistingAdvisors) {
                    advised.addAdvisor(0, this.advisor);
                } else {
                    // 獲取已有切面
                    Advisor[] advisorArr = advised.getAdvisors();
                    // 遍歷已有切面與當(dāng)前切面的order值比較, 找到第一個比當(dāng)前切面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()) {
                                // 當(dāng)前攔截器的執(zhí)行優(yōu)先級高于數(shù)組中當(dāng)前循環(huán)的這個優(yōu)先級, 所以在這個位置插入當(dāng)前攔截器
                                curAdvisorPos = i;
                                break;
                            }
                        }
                    }
                    if (null == curAdvisorPos) {
                        advised.addAdvisor(this.advisor);
                    } else {
                        // 在第一個比當(dāng)前切面order值大位置插入當(dāng)前切面, 其它切面一次往后移一位
                        advised.addAdvisor(curAdvisorPos, this.advisor);
                    }
                }
                return bean;
            }
        }

        if (isEligible(bean, beanName)) {
            // 如果是Eligible合適的,且還不是被代理的類剖煌,則創(chuàng)建一個代理類的實例并返回
            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;
    }
}

三. 解決問題之后的總結(jié)

MethodValidationPostProcessor, MethodValidationInterceptor是做什么的?

MethodValidationPostProcessor 繼承關(guān)系

image.png

MethodValidationInterceptor 繼承關(guān)系

image.png

我是如何一步一步解決這個問題的?

首先無腦搜索了一番

百度, 谷歌, 必應(yīng)... 各種關(guān)鍵詞, 英文的, 中文的, 慢慢查. 我覺得應(yīng)該也有人會有類似的問題. 確實有類似問題, 但不是說使用@Order注解就是實現(xiàn)Ordered接口. 或者一些答非所問. 情況似乎陷入了僵局. 不死心, 又搜了幾次, 還是和之前一樣, 沒有進展, 然后隔了幾天, 還是不死心, 又搜了幾次, 還是失敗.

這似乎和bean實例化有關(guān)

于是搜索bean實例化過程. 期望會有直接的答案或發(fā)現(xiàn)一些有啟發(fā)性的文章. 很好, 完全沒有搜到有用的信息. 大多是一些大大們相互借鑒的文章. 可是不甘心啊, 于是我找了點關(guān)于spring原理的視頻. 下完視頻用了2-3天, 硬著頭皮聽了2天, 很好, 視頻里就是帶著我們以debug方式看源碼, 但我感覺他主要還是讓我們背源代碼, 去背類名, 方法名. 唯一有點意義的可能只有一句話: spring的套路是把真正重要的邏輯都放到doXxx方法中, 看源碼先打斷點, 看返回值在哪個地方產(chǎn)生了變化, 那個讓返回值變化的地方, 可能就是需要重點關(guān)注的地方, 情況似乎又陷入了僵局. 可是, 我覺得我的方向應(yīng)該是沒錯的.

無意間發(fā)現(xiàn)原來spring的代理對象結(jié)構(gòu)是這樣的

image.png

image.png

并且參數(shù)校驗切面也在里面, 那么我是不是可以大膽假設(shè)這個advisors, 這里面存放了該bean實例所有的切面, 并且存放順序就是這些切面的執(zhí)行順序.

小心求證: 在自定義切面中加斷點, 在方法驗證切面中加斷點, 發(fā)現(xiàn)他們的執(zhí)行順序確實和這里的存放順序一致, 那么我是不是可以再次大膽假設(shè), 只要我解決了advisors數(shù)據(jù)的存放順序問題, 就能解決參數(shù)驗證切面的執(zhí)行順序問題?

advisors這個屬性的屬性值是什么時候被填充的?

  1. 首先要解決的是, spring的源碼是在哪個地方創(chuàng)建出了代理對象?
  2. 然后才是advisors這個屬性的屬性值是什么時候被填充的?

關(guān)于第一點, 我知道對象的創(chuàng)建肯定需要調(diào)用構(gòu)造方法, 即使是spring要創(chuàng)建對象, 也需要調(diào)用構(gòu)造方法, 于是我就在在無參構(gòu)造方法中打了個斷點, 然后觀察Frames里的內(nèi)容

image.png

找spring包的方法, 并回想起視頻里說的, spring源碼套路是將真正重要的邏輯都放到doXxx方法中, 并根據(jù)方法名, 鎖定到了doCreateBean方法

image.png
image.png

通過斷點我發(fā)現(xiàn)559行獲取到了原始對象

doCreate方法節(jié)選

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) {
                        // 重點: 創(chuàng)建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);
                        // 執(zhí)行完下面這句代碼之后, 我發(fā)現(xiàn), bean實例發(fā)生了變化, 不再是真實的bean實例對象, 而是轉(zhuǎn)換成了代理對象, 就是我最開始截圖的那個結(jié)構(gòu), 并且已經(jīng)填充好了`advisors`值, 于是我感覺這里應(yīng)該就是轉(zhuǎn)折點了, 繼續(xù)跟進
            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()) {
                        // 這里執(zhí)行完之后, bean對象從直接對象, 變成了代理對象, 繼續(xù)跟進
            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類的對象的時候, 停了下來, 于是我跟進代碼, 終于發(fā)現(xiàn)方法驗證切面就是在WinterMethodValidationPostProcessor實例的postProcessAfterInitialization方法中進行填充的, 遂改變原始邏輯, 加入排序邏輯, 問題最終得以解決.
            Object current = processor.postProcessBeforeInitialization(result, beanName);
            if (current == null) {
                return result;
            }
            result = current;
        }
        return result;
    }

后記

不容易啊, 我的堅持與執(zhí)著這一次終于有了一個好的結(jié)果. 我感覺以后分析類似的問題, 我都有思路了. 不至于像以前一樣盲目搜索, 盲目提問了. 這應(yīng)該是個好的開始. 開心! 開心! 為防忘記, 遂有此記錄!!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市献丑,隨后出現(xiàn)的幾起案子末捣,更是在濱河造成了極大的恐慌,老刑警劉巖创橄,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件箩做,死亡現(xiàn)場離奇詭異,居然都是意外死亡妥畏,警方通過查閱死者的電腦和手機邦邦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來醉蚁,“玉大人燃辖,你說我怎么就攤上這事⊥鳎” “怎么了黔龟?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我氏身,道長巍棱,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任蛋欣,我火速辦了婚禮航徙,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘陷虎。我一直安慰自己到踏,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布尚猿。 她就那樣靜靜地躺著窝稿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪谊路。 梳的紋絲不亂的頭發(fā)上讹躯,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天,我揣著相機與錄音缠劝,去河邊找鬼潮梯。 笑死,一個胖子當(dāng)著我的面吹牛惨恭,可吹牛的內(nèi)容都是我干的秉馏。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼脱羡,長吁一口氣:“原來是場噩夢啊……” “哼萝究!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起锉罐,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤帆竹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后脓规,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體栽连,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年侨舆,在試婚紗的時候發(fā)現(xiàn)自己被綠了秒紧。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡挨下,死狀恐怖熔恢,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情臭笆,我是刑警寧澤叙淌,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布秤掌,位于F島的核電站,受9級特大地震影響鹰霍,放射性物質(zhì)發(fā)生泄漏机杜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一衅谷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧似将,春花似錦获黔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至腋舌,卻和暖如春盏触,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背块饺。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工赞辩, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人授艰。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓辨嗽,卻偏偏與公主長得像,于是被迫代替她去往敵國和親淮腾。 傳聞我的和親對象是個殘疾皇子糟需,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容