34--SpringAop獲取增強(qiáng)(二)

在上一篇結(jié)尾拴事,我們得到了增強(qiáng)的提取工作交給了List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);。接著分析惊科。

1. getAdvisors獲取增強(qiáng)簡(jiǎn)析
@Override
public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {
    // 1讼撒、預(yù)處理工作,包括獲取切面類,名稱乓序,驗(yàn)證等
    Class<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
    String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();
    validate(aspectClass);

    // 我們需要用一個(gè)裝飾器包裝MetadataAwareAspectInstanceFactory隔箍,這樣它只會(huì)實(shí)例化一次谓娃。
    MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory =
            new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory);

    // 2、提取增強(qiáng)
    // 2.1蜒滩、獲取切面類的所有方法滨达,循環(huán)判斷提取合適的切入點(diǎn),并創(chuàng)建增強(qiáng)
    List<Advisor> advisors = new ArrayList<>();
    for (Method method : getAdvisorMethods(aspectClass)) {
        // 獲取增強(qiáng)
        Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName);
        if (advisor != null) {
            advisors.add(advisor);
        }
    }

    // If it's a per target aspect, emit the dummy instantiating aspect.
    // 2.2俯艰、處理perthis和pertarget切面類
    if (!advisors.isEmpty() && lazySingletonAspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
        // 創(chuàng)建SyntheticInstantiationAdvisor實(shí)例
        Advisor instantiationAdvisor = new SyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory);
        // 將SyntheticInstantiationAdvisor實(shí)例加入到advisors集合首位捡遍,注意:不是替換
        advisors.add(0, instantiationAdvisor);
    }

    // Find introduction fields.
    // 2.3、處理引入竹握,獲取所有的引入并循環(huán)創(chuàng)建DeclareParentsAdvisor
    for (Field field : aspectClass.getDeclaredFields()) {
        Advisor advisor = getDeclareParentsAdvisor(field);
        if (advisor != null) {
            advisors.add(advisor);
        }
    }

    return advisors;
}

在該方法中提取工作一共分為了三步:提取普通增強(qiáng)画株、處理處理perthis和pertarget、引介增強(qiáng)。篇幅有限谓传,只分析普通增強(qiáng)的處理過程,該過程也是大家最關(guān)心的過程蜈项。來看代碼:

@Override
@Nullable
public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory,
        int declarationOrderInAspect, String aspectName) {

    // 1、驗(yàn)證
    validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());

    // 2续挟、提取切入點(diǎn)
    AspectJExpressionPointcut expressionPointcut = getPointcut(
            candidateAdviceMethod,
            aspectInstanceFactory.getAspectMetadata().getAspectClass());

    if (expressionPointcut == null) {
        return null;
    }

    // 3紧卒、創(chuàng)建增強(qiáng)
    return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,
            this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
}

在該方法中,終于看到了核心的切點(diǎn)提取和創(chuàng)建增強(qiáng)诗祸。下面分別來看這兩步是如何實(shí)現(xiàn)的跑芳。

2.提取切入點(diǎn)
@Nullable
private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) {
    // 1、從候選切入點(diǎn)上找出增強(qiáng)表達(dá)式
    AspectJAnnotation<?> aspectJAnnotation = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
    if (aspectJAnnotation == null) {
        return null;
    }

    // 2直颅、創(chuàng)建AspectJExpressionPointcut對(duì)象
    AspectJExpressionPointcut ajexp = new AspectJExpressionPointcut(candidateAspectClass, new String[0], new Class<?>[0]);
    ajexp.setExpression(aspectJAnnotation.getPointcutExpression());
    if (this.beanFactory != null) {
        ajexp.setBeanFactory(this.beanFactory);
    }
    return ajexp;
}
2.1 從候選切入點(diǎn)上找出增強(qiáng)表達(dá)式
/**
 * Find and return the first AspectJ annotation on the given method
 * (there <i>should</i> only be one anyway...).
 */
@SuppressWarnings("unchecked")
@Nullable
protected static AspectJAnnotation<?> findAspectJAnnotationOnMethod(Method method) {
    Class<?>[] classesToLookFor = new Class<?>[] {
            Before.class, Around.class, After.class, AfterReturning.class, AfterThrowing.class, Pointcut.class};
    for (Class<?> c : classesToLookFor) {
        AspectJAnnotation<?> foundAnnotation = findAnnotation(method, (Class<Annotation>) c);
        if (foundAnnotation != null) {
            return foundAnnotation;
        }
    }
    return null;
}

該過程主要通過findAnnotation方法完成博个,但是該方法調(diào)用較深,也不屬于我們分析的范疇际乘,感興趣的同學(xué)可以自己跟蹤調(diào)試坡倔。

2.2 創(chuàng)建AspectJExpressionPointcut對(duì)象
/**
 * Create a new AspectJExpressionPointcut with the given settings.
 * @param declarationScope the declaration scope for the pointcut
 * @param paramNames the parameter names for the pointcut
 * @param paramTypes the parameter types for the pointcut
 */
public AspectJExpressionPointcut(Class<?> declarationScope, String[] paramNames, Class<?>[] paramTypes) {
    this.pointcutDeclarationScope = declarationScope;
    if (paramNames.length != paramTypes.length) {
        throw new IllegalStateException(
                "Number of pointcut parameter names must match number of pointcut parameter types");
    }
    this.pointcutParameterNames = paramNames;
    this.pointcutParameterTypes = paramTypes;
}

該創(chuàng)建過程比較簡(jiǎn)單,將提取的切點(diǎn)表達(dá)式的信息實(shí)例化為AspectJExpressionPointcut對(duì)象即可脖含。

3.創(chuàng)建增強(qiáng)
public InstantiationModelAwarePointcutAdvisorImpl(
            AspectJExpressionPointcut declaredPointcut,
            Method aspectJAdviceMethod,
            AspectJAdvisorFactory aspectJAdvisorFactory,
            MetadataAwareAspectInstanceFactory aspectInstanceFactory,
            int declarationOrder,
            String aspectName) {

    this.declaredPointcut = declaredPointcut;
    this.declaringClass = aspectJAdviceMethod.getDeclaringClass();
    this.methodName = aspectJAdviceMethod.getName();
    this.parameterTypes = aspectJAdviceMethod.getParameterTypes();
    this.aspectJAdviceMethod = aspectJAdviceMethod;
    this.aspectJAdvisorFactory = aspectJAdvisorFactory;
    this.aspectInstanceFactory = aspectInstanceFactory;
    this.declarationOrder = declarationOrder;
    this.aspectName = aspectName;

    // 1罪塔、延遲初始化
    // 在Spring AOP中,切面類的實(shí)例只有一個(gè)养葵,比如前面我們一直使用的MyAspect類征堪,
    // 假設(shè)我們使用的切面類需要具有某種狀態(tài),以適用某些特殊情況的使用关拒,比如多線程環(huán)境佃蚜,此時(shí)單例的切面類就不符合我們的要求了。
    // 在Spring AOP中着绊,切面類默認(rèn)都是單例的谐算,但其還支持另外兩種多例的切面實(shí)例的切面,即perthis和pertarget归露,
    // 需要注意的是perthis和pertarget都是使用在切面類的@Aspect注解中的洲脂。
    // 這里perthis和pertarget表達(dá)式中都是指定一個(gè)切面表達(dá)式,其語義與前面講解的this和target非常的相似剧包,
    // perthis表示如果某個(gè)類的代理類符合其指定的切面表達(dá)式恐锦,那么就會(huì)為每個(gè)符合條件的目標(biāo)類都聲明一個(gè)切面實(shí)例;
    // pertarget表示如果某個(gè)目標(biāo)類符合其指定的切面表達(dá)式疆液,那么就會(huì)為每個(gè)符合條件的類聲明一個(gè)切面實(shí)例一铅。
    // 從上面的語義可以看出,perthis和pertarget的含義是非常相似的堕油。如下是perthis和pertarget的使用語法:
    // perthis(pointcut-expression)
    // pertarget(pointcut-expression)
    if (aspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
        // Static part of the pointcut is a lazy type.
        Pointcut preInstantiationPointcut = Pointcuts.union(
                aspectInstanceFactory.getAspectMetadata().getPerClausePointcut(),
                this.declaredPointcut);

        // Make it dynamic: must mutate from pre-instantiation to post-instantiation state.
        // If it's not a dynamic pointcut, it may be optimized out
        // by the Spring AOP infrastructure after the first evaluation.
        this.pointcut = new PerTargetInstantiationModelPointcut(
                this.declaredPointcut,
                preInstantiationPointcut,
                aspectInstanceFactory);
        this.lazy = true;
    }
    // 2潘飘、立刻初始化
    else {
        // A singleton aspect.
        this.pointcut = this.declaredPointcut;
        this.lazy = false;
        // 初始化增強(qiáng)
        this.instantiatedAdvice = instantiateAdvice(this.declaredPointcut);
    }
}

創(chuàng)建過程中又涉及到perthis和pertarget肮之,無需理會(huì),還是先看單例模式下的創(chuàng)建過程:

/**
 * 根據(jù)pointcut初始化增強(qiáng)
 * @param pointcut
 * @return
 */
private Advice instantiateAdvice(AspectJExpressionPointcut pointcut) {
    Advice advice = this.aspectJAdvisorFactory.getAdvice(
            this.aspectJAdviceMethod, pointcut,
            this.aspectInstanceFactory,
            this.declarationOrder,
            this.aspectName);
    return (advice != null ? advice : EMPTY_ADVICE);
}
@Override
@Nullable
public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut,
        MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {

    // 1卜录、獲取增強(qiáng)之前的處理
    Class<?> candidateAspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
    validate(candidateAspectClass);

    AspectJAnnotation<?> aspectJAnnotation = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
    if (aspectJAnnotation == null) {
        return null;
    }

    // If we get here, we know we have an AspectJ method.
    // Check that it's an AspectJ-annotated class
    if (!isAspect(candidateAspectClass)) {
        throw new AopConfigException("Advice must be declared inside an aspect type: " +
                "Offending method '" + candidateAdviceMethod + "' in class [" +
                candidateAspectClass.getName() + "]");
    }


    // 2局骤、針對(duì)各種不同的增強(qiáng),做不同的處理
    AbstractAspectJAdvice springAdvice;
    switch (aspectJAnnotation.getAnnotationType()) {
            // 1暴凑、前置增強(qiáng)
        case AtBefore:
            springAdvice = new AspectJMethodBeforeAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
            break;
            // 2、后置增強(qiáng)
        case AtAfter:
            springAdvice = new AspectJAfterAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
            break;
            // 3赘来、后置返回增強(qiáng)
        case AtAfterReturning:
            springAdvice = new AspectJAfterReturningAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
            AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation();
            if (StringUtils.hasText(afterReturningAnnotation.returning())) {
                springAdvice.setReturningName(afterReturningAnnotation.returning());
            }
            break;
            // 4现喳、后置異常增強(qiáng)
        case AtAfterThrowing:
            springAdvice = new AspectJAfterThrowingAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
            AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation();
            if (StringUtils.hasText(afterThrowingAnnotation.throwing())) {
                springAdvice.setThrowingName(afterThrowingAnnotation.throwing());
            }
            break;
            // 5、環(huán)繞增強(qiáng)
        case AtAround:
            springAdvice = new AspectJAroundAdvice(
                    candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
            break;
            // 6犬辰、如果是Pointcut嗦篱,則不做處理
        case AtPointcut:
            return null;
            // 7、未能滿足case條件幌缝,拋出異常
        default:
            throw new UnsupportedOperationException("Unsupported advice type on method: " + candidateAdviceMethod);
    }

    // 3灸促、獲取增強(qiáng)方法之后,對(duì)增強(qiáng)方法進(jìn)行配置
    springAdvice.setAspectName(aspectName);
    springAdvice.setDeclarationOrder(declarationOrder);
    String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod);
    if (argNames != null) {
        springAdvice.setArgumentNamesFromStringArray(argNames);
    }
    springAdvice.calculateArgumentBindings();

    return springAdvice;
}

看到這里涵卵,是不是有種豁然開朗的感覺浴栽,Spring針對(duì)各種不同的增強(qiáng)創(chuàng)建過程,做了不同的處理轿偎。到這里Spring創(chuàng)建增強(qiáng)的過程就完成了典鸡,對(duì)于不同的增強(qiáng)類型創(chuàng)建,大家可以自己debug跟蹤坏晦,這里不一一贅述了萝玷。

4.關(guān)于Advisor的疑問

這兩篇在介紹獲取增強(qiáng),但是最受獲取到的并不是Advice而是Advisor昆婿,可能大家會(huì)有所疑問球碉。舉例說明一下:
如果我們定義了一個(gè)DogAspect類,并用@AspectJ對(duì)其進(jìn)行注解,那么該類僅僅代表一個(gè)切面類仓蛆,會(huì)被Spring掃描并解析睁冬,僅此而已,該類不代表SpringAop概念中的切面多律。那么Spring如果通過解析該類得到具體的切面呢?

首先,關(guān)于SpringAop中的切面概念痴突,可以理解為 切面=連接點(diǎn)+增強(qiáng)

其次,而標(biāo)記了@AspectJ注解的類在被Spring解析的時(shí)候,

  1. 提取該類的方法上的切點(diǎn)表達(dá)式注解:例如-->@Pointcut("execution(* com.lyc.cn.v2.day07..(..))")狼荞,解析之后,就可以的到具體的切點(diǎn).
  2. 提取該類的方法上的增強(qiáng)注解:例如:@Before("test()")解析之后,就可以得到具體的增強(qiáng)代碼

最后,通過第一步和第二步的操作,就可以得到切點(diǎn)+增強(qiáng),那么自然就構(gòu)成了一個(gè)切面

但是Advisor接口里只包含了一個(gè)Advice,并且Advisor一般不直接提供給用戶使用,所以這里也可以理解為獲取增強(qiáng)辽装,當(dāng)然如果理解為切面也是沒有問題的。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末相味,一起剝皮案震驚了整個(gè)濱河市拾积,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖拓巧,帶你破解...
    沈念sama閱讀 218,036評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件斯碌,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡肛度,警方通過查閱死者的電腦和手機(jī)傻唾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來承耿,“玉大人冠骄,你說我怎么就攤上這事〖哟” “怎么了凛辣?”我有些...
    開封第一講書人閱讀 164,411評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)职烧。 經(jīng)常有香客問我扁誓,道長(zhǎng),這世上最難降的妖魔是什么蚀之? 我笑而不...
    開封第一講書人閱讀 58,622評(píng)論 1 293
  • 正文 為了忘掉前任蝗敢,我火速辦了婚禮,結(jié)果婚禮上恬总,老公的妹妹穿的比我還像新娘前普。我一直安慰自己,他們只是感情好壹堰,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評(píng)論 6 392
  • 文/花漫 我一把揭開白布拭卿。 她就那樣靜靜地躺著,像睡著了一般贱纠。 火紅的嫁衣襯著肌膚如雪峻厚。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,521評(píng)論 1 304
  • 那天谆焊,我揣著相機(jī)與錄音惠桃,去河邊找鬼。 笑死辖试,一個(gè)胖子當(dāng)著我的面吹牛辜王,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播罐孝,決...
    沈念sama閱讀 40,288評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼呐馆,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了莲兢?” 一聲冷哼從身側(cè)響起汹来,我...
    開封第一講書人閱讀 39,200評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤续膳,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后收班,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體坟岔,經(jīng)...
    沈念sama閱讀 45,644評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評(píng)論 3 336
  • 正文 我和宋清朗相戀三年摔桦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了社付。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,953評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡邻耕,死狀恐怖瘦穆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情赊豌,我是刑警寧澤,帶...
    沈念sama閱讀 35,673評(píng)論 5 346
  • 正文 年R本政府宣布绵咱,位于F島的核電站碘饼,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏悲伶。R本人自食惡果不足惜艾恼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望麸锉。 院中可真熱鬧钠绍,春花似錦、人聲如沸花沉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽碱屁。三九已至磷脯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間娩脾,已是汗流浹背赵誓。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留柿赊,地道東北人俩功。 一個(gè)月前我還...
    沈念sama閱讀 48,119評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像碰声,于是被迫代替她去往敵國(guó)和親诡蜓。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評(píng)論 2 355

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理奥邮,服務(wù)發(fā)現(xiàn)万牺,斷路器罗珍,智...
    卡卡羅2017閱讀 134,656評(píng)論 18 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,811評(píng)論 6 342
  • 本文是我自己在秋招復(fù)習(xí)時(shí)的讀書筆記,整理的知識(shí)點(diǎn)脚粟,也是為了防止忘記覆旱,尊重勞動(dòng)成果,轉(zhuǎn)載注明出處哦核无!如果你也喜歡扣唱,那...
    波波波先森閱讀 12,291評(píng)論 6 86
  • 01: 五天,放在一個(gè)星期里面好長(zhǎng)团南,放在一年里又好短噪沙。我們兩年前從財(cái)經(jīng)畢業(yè),五天前才第一次見面吐根,每天在群里聊得熱火...
    陳哇噻aa閱讀 556評(píng)論 2 7
  • 還有一種情況是拷橘,家長(zhǎng)不是聽從醫(yī)生的話局义,看孩子是否需要化驗(yàn),而是一到醫(yī)院就要求給孩子化驗(yàn)冗疮,不管孩子是什么問題萄唇。 按照...
    楊金社閱讀 214評(píng)論 1 0