向您圖文并茂生動講解Spring AOP 源碼(2)

前言

往期文章:

在上一章向您生動地講解Spring AOP 源碼(1)中蔚万,作者介紹了【開啟AOP自動代理的玄機】和【自動代理的觸發(fā)時機】儒喊。

在本章中,作者會向您介紹逛艰,Spring AOP 是如何解析我們配置的Aspect座咆,生成 Advisors 鏈的?

閑話不多說,讓我們直接開始瞬捕。

獲取對應 Bean 適配的Advisors 鏈

獲取對應 Bean 適配的 Advisors 鏈歉嗓,分為兩步丰介。

  1. 獲取容器所有的 advisors 作為候選,即解析Spring 容器中所有 Aspect 類中的 advice 方法鉴分,包裝成 advisor哮幢;
  2. 從候選的 Advisors 中篩選出適配當前 Bean的 Advisors 鏈;

未免讀者閱讀不連貫志珍,我們重新貼一下上篇文章中我們最后講解的一段源碼橙垢,由此繼續(xù)往下講述。

源碼位置:AbstractAutoProxyCreator#wrapIfNecessary(..)

wrapIfNecessary

源碼位置:AspectJAwareAdvisorAutoProxyCreator#shouldSkip(..)

shouldSkip.png

源碼位置:AbstractAdvisorAutoProxyCreator#getAdvicesAndAdvisorsForBean(..)碴裙、AbstractAdvisorAutoProxyCreator#findEligibleAdvisors(..)

getAdvicesAndAdvisorsForBean.png

可以看到兩個方法都調(diào)用了findCandidateAdvisors()方法钢悲,也就是去獲取候選的 Advisors,我們進去看看里面干了什么舔株。

1. 獲取候選的 Advisors

findCandidateAdvisors.png
image.png
image.png

從Debug 出來的線程椵毫眨可以看出,AnnotationAwareAspectJAutoProxyCreator 通過 持有 BeanFactoryAspectJAdvisorsBuilder對象载慈,來獲取Advisor鏈惭等。

再往下看。源碼位置:BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisors

內(nèi)容較長办铡,請大家跟著注釋耐心看下去辞做。

buildAspectJAdvisors1.png

這個方法除了等下要講 advisorFactory.getAdvisors(..)以外,需要注意的就是其為了避免每次都去獲取所有的beanName寡具,解析判斷秤茅,引入了緩存的機制;還有就是Aspect類是根據(jù)Spring Bean 是否被 @Aspect注解修飾來判斷的童叠。

我們接下去看框喳,真正的去獲取我們的Advisor的方法,this.advisorFactory.getAdvisors(factory) 方法如下:

源碼位置:ReflectiveAspectJAdvisorFactory#getAdvisors(..)

@Override
    public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {
        //獲取我們的標記為Aspect的類
        Class<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
        //獲取我們的切面類的名稱
        String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();
        //校驗我們的切面類
        validate(aspectClass);

        // 這里使用了裝飾器模式厦坛,目的是使MetadataAwareAspectInstanceFactory只實例化一次
        MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory =
                new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory);

        List<Advisor> advisors = new ArrayList<>();
        //獲取到切面類中的所有方法五垮,但是該方法不會解析到標注了@PointCut注解的方法
        for (Method method : getAdvisorMethods(aspectClass)) {
            // 循環(huán)解析我們切面中的方法
            // 獲取當前方法的增強器Advisor
            Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName);
            if (advisor != null) {
                advisors.add(advisor);
            }
        }
        // 如果尋找的增強器不為空而且又配置了增強延遲初始化那么需要在首位加入同步實例化增強器
        if (!advisors.isEmpty() && lazySingletonAspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
            Advisor instantiationAdvisor = new SyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory);
            advisors.add(0, instantiationAdvisor);
        }
        // Find introduction fields.
        // 獲取 DeclareParents 注解
        for (Field field : aspectClass.getDeclaredFields()) {
            Advisor advisor = getDeclareParentsAdvisor(field);
            if (advisor != null) {
                advisors.add(advisor);
            }
        }

        return advisors;
    }
getAdvisors.png

解析advice 方法成 advisor對象,

源碼位置:ReflectiveAspectJAdvisorFactory#getAdvisor(..)

    @Override
    public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory,
                              int declarationOrderInAspect, String aspectName) {
        //校驗
        validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());
        //獲取當前方法的切入點 pointcut
        AspectJExpressionPointcut expressionPointcut = getPointcut(candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());
        if (expressionPointcut == null) {
            return null;
        }
        //根據(jù)切點信息生成 advisor
        return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod, this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
    }
getAdvisor.png
生成advisor

如何生成advisor也值得一提杜秸。

注釋方式下放仗,我們聲明的advice 方法是這樣的。(不熟悉范例的可以看上一篇文章)

@Aspect
@Component
public class PointCutConfig {
    // ... 省略
    // service 層
    @Pointcut("within(ric.study.demo.aop.svc..*)")
    public void inSvcLayer() {}
    // ... 省略
}
@Aspect
@Component
public class GlobalAopAdvice {

    @Before("ric.study.demo.aop.PointCutConfig.inSvcLayer()")
    public void logBeforeSvc(JoinPoint joinPoint) {
        System.out.println("在service 層前打印日志");
        System.out.println("攔截的service 方法的方法簽名: " + joinPoint.getSignature());
    }
}
advice方法.png

生成之后是這樣的撬碟,

image.png

advice 的對象類型是InstantiationModelAwarePointcutAdvisorImpl诞挨,我們來看下生成advisor時調(diào)用的這個類的構(gòu)造函數(shù)莉撇,

InstantiationModelAwarePointcutAdvisorImpl.png

里面包括了一個重要的方法instantiateAdvice,即創(chuàng)建Advice亭姥,這也是我要強調(diào)的重點稼钩,怎么解析出來一個advice。

源碼位置:InstantiationModelAwarePointcutAdvisorImpl#instantiateAdvice(..)

源碼位置:ReflectiveAspectJAdvisorFactory#getAdvice(..)

第一次解析Advisor的時機

關(guān)于第一次解析Advisor的時機达罗,我剛開始也搞混了坝撑。所以在這里說明一下。

這個圖是之前貼過的粮揉,第一次觸發(fā)的截圖巡李。

AnnotationAwareAspectJAutoProxyCreator繼承了AbstractAutoProxyCreator實現(xiàn)了InstantiationAwareBeanPostProcessor接口:

image.png

會在生成target class 對象之前,調(diào)用 postProcessBeforeInstantiation(..)扶认,具體的代碼可以去看AbstractAutowireCapableBeanFactory#createBean(..)方法侨拦。我們這邊直接看一下 postProcessBeforeInstantiation(..)AbstractAutoProxyCreator中的實現(xiàn)。

   /**
     * 在創(chuàng)建Bean的流程中還沒調(diào)用構(gòu)造器來實例化Bean的時候進行調(diào)用(實例化前后)
     * AOP解析切面在這里完成
     */
    @Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        // 構(gòu)建我們的緩存key
        Object cacheKey = getCacheKey(beanClass, beanName);

        if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
            // 如果被解析過直接返回
            if (this.advisedBeans.containsKey(cacheKey)) {
                return null;
            }
            // 判斷是不是基礎(chǔ)的Bean(Advice辐宾、PointCut狱从、Advisor、AopInfrastructureBean)是就直接跳過
            // 判斷是不是應該跳過 (AOP解析直接解析出我們的切面信息(并且把我們的切面信息進行緩存)叠纹,
            // 而事務在這里是不會解析的)
            if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
                this.advisedBeans.put(cacheKey, Boolean.FALSE);
                return null;
            }
        }

        TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
        if (targetSource != null) {
            if (StringUtils.hasLength(beanName)) {
                this.targetSourcedBeans.add(beanName);
            }
            Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
            Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
            this.proxyTypes.put(cacheKey, proxy.getClass());
            return proxy;
        }

        return null;
    }

2. 篩選 出 適配當前類的 Advisors

這里來一條分割線季研,至此,findCandidateAdvisors()算是解析完畢了誉察。

但是我們通過這個方法只是獲得了所有候選的advisors与涡,還記得我們這一節(jié)的標題不?

【獲取對應 Bean 適配的Advisors 鏈】

那么我們下一步就是要過濾出適配當前這個 target class 的 advisors持偏。

findEligibleAdvisors.png

也就是上圖的findAdvisorsThatCanApply(..)

Search the given candidate Advisors to find all Advisors that can apply to the specified bean.

從給出的候選 Advisors 找出可以作用在 當前bean 的 Advisors 鏈

Debug階段驼卖,篩選之前的候選 advisors 和篩選之后的可用的 advisors,

源碼位置:AbstractAdvisorAutoProxyCreator#findAdvisorsThatCanApply(..)

findAdvisorsThatCanApply.png

源碼位置:AopUtils#findAdvisorsThatCanApply(..)

我們接下去看篩選的關(guān)鍵方法``AopUtils#canApply(..)`

篩選的工作主要由 ClassFilter 和 MethodMatcher 完成鸿秆,比如AspectJExpressionPointcut的實現(xiàn)了ClassFilter和MethodMatcher接口酌畜,最終由AspectJ表達式解析,這個地方就復雜了卿叽,也不是核心點檩奠。


又是一條分割線。

到這里之后附帽,Advisor的篩選過程我們算是講完了。

findEligibleAdvisors2.png

經(jīng)過排序之后井誉,我們算是拿到了這個目標類使用的 Advisors 鏈蕉扮。

現(xiàn)在,讓我們再回到最初的起點颗圣。

源碼位置:AbstractAutoProxyCreator#wrapIfNecessary(..)

wrapIfNecessary

小結(jié)

到這里喳钟,大家可以回顧一下屁使,我們總算是把TODO-1【Spring AOP 如何 獲取對應 Bean 適配的Advisors 鏈】介紹完畢了,總結(jié)一下核心邏輯就是:

  1. 獲取當前 IoC 容器中所有的 Aspect 類
  2. 給 每個Aspect 類的advice 方法創(chuàng)建一個 Spring Advisor奔则,這一步又能細分為
    1. 遍歷所有advice 方法
    2. 解析方法的注解和pointcut
    3. 實例化 Advisor 對象
  3. 獲取到 候選的 Advisors蛮寂,并且緩存起來,方便下一次直接獲取
  4. 從候選的 Advisors 中篩選出與目標類 適配的Advisor
    1. 獲取到 Advisor 的 切入點 pointcut
    2. 獲取到 當前 target 類 所有的 public 方法
    3. 遍歷方法易茬,通過 切入點 的 methodMatcher 匹配當前方法酬蹋,只有有一個匹配成功就相當于當前的Advisor 適配
  5. 對篩選之后的 Advisor 鏈進行排序
  6. 結(jié)束

下一節(jié)中,我們會介紹 【代理類的創(chuàng)建過程】抽莱,我們下次再會范抓。

如果本文有幫助到你,希望能點個贊食铐,這是對我的最大動力匕垫。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市虐呻,隨后出現(xiàn)的幾起案子象泵,更是在濱河造成了極大的恐慌,老刑警劉巖斟叼,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蝎毡,死亡現(xiàn)場離奇詭異跺株,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門姐刁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人贫途,你說我怎么就攤上這事擂达。” “怎么了萤悴?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵瘾腰,是天一觀的道長。 經(jīng)常有香客問我覆履,道長蹋盆,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任硝全,我火速辦了婚禮栖雾,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘伟众。我一直安慰自己析藕,他們只是感情好,可當我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布凳厢。 她就那樣靜靜地躺著账胧,像睡著了一般竞慢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上治泥,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天筹煮,我揣著相機與錄音,去河邊找鬼居夹。 笑死败潦,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的吮播。 我是一名探鬼主播变屁,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼意狠!你這毒婦竟也來了粟关?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤环戈,失蹤者是張志新(化名)和其女友劉穎闷板,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體院塞,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡遮晚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了拦止。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片县遣。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖汹族,靈堂內(nèi)的尸體忽然破棺而出萧求,到底是詐尸還是另有隱情,我是刑警寧澤顶瞒,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布夸政,位于F島的核電站,受9級特大地震影響榴徐,放射性物質(zhì)發(fā)生泄漏守问。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一坑资、第九天 我趴在偏房一處隱蔽的房頂上張望耗帕。 院中可真熱鬧,春花似錦袱贮、人聲如沸仿便。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽探越。三九已至,卻和暖如春窑业,著一層夾襖步出監(jiān)牢的瞬間钦幔,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工常柄, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留鲤氢,地道東北人。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓西潘,卻偏偏與公主長得像卷玉,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子喷市,可洞房花燭夜當晚...
    茶點故事閱讀 44,614評論 2 353

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