Spring源碼分析(七)SpringAOP生成代理類及執(zhí)行的過程

1于样、AOP的入口

上一節(jié)我們在分析解析AOP標(biāo)簽的時候,第一步就是注冊了一個類AspectJAwareAdvisorAutoProxyCreator掩驱,我們說它是AOP的入口類芒划。為什么這樣說呢?
來看它父類的父類AbstractAutoProxyCreator欧穴,它繼承了BeanPostProcessor接口民逼。
那么,有兩個方法肯定要被調(diào)用到postProcessBeforeInitialization苔可、postProcessAfterInitialization缴挖。一個在依賴注入完成之前調(diào)用,一個在之后調(diào)用焚辅。

public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    if (bean != null) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        if (!this.earlyProxyReferences.contains(cacheKey)) {
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}

wrapIfNecessary方法則是真正產(chǎn)生代理的地方映屋,我們先看下它的內(nèi)部實(shí)現(xiàn)。

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    // Create proxy if we have advice.
    //先看它的注釋同蜻,大意說:如果有通知棚点,就創(chuàng)建代理。
    //其實(shí)就是在bean.getClass()找到所有的通知和advisor
    //這里面其實(shí)又分為兩個步驟:
    //第一湾蔓,在Bean工廠找到所有的Advisor 第二瘫析,根據(jù)beanClass和Pointcut去做匹配
    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
    if (specificInterceptors != DO_NOT_PROXY) {
        this.advisedBeans.put(cacheKey, Boolean.TRUE);
        //真正創(chuàng)建代理并返回,這時候返回的就是代理類了,把真實(shí)的bean替換掉
        Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
        this.proxyTypes.put(cacheKey, proxy.getClass());
        return proxy;
    }
    this.advisedBeans.put(cacheKey, Boolean.FALSE);
    return bean;
}

看到上面的源碼贬循,整個過程就分為了兩步咸包,匹配和創(chuàng)建返回。這樣就完成了代理類的替換杖虾,是不是有點(diǎn)偷梁換柱的感覺烂瘫。

匹配

先是找到所有的Advisor,這個比較簡單奇适。因為我們知道坟比,在解析的時候就把配置的通知封裝成advisor注冊了進(jìn)去。首先拿到beanDefinitionNames容器所有beanName嚷往,循環(huán)判斷bean的類型是不是advisor接口的類型葛账,符合條件返回。

public List<Advisor> findAdvisorBeans() {
    // Determine list of advisor bean names, if not cached already.
    String[] advisorNames = null;
    
    //先拿到beanName
    advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
            this.beanFactory, Advisor.class, true, false);
        
    List<Advisor> advisors = new LinkedList<Advisor>();
    for (String name : advisorNames) {
        if (isEligibleBean(name)) {     
            try {
                //再從Bean工廠中拿bean的實(shí)例
                advisors.add(this.beanFactory.getBean(name, Advisor.class));
            }
        }
    }
    //返回的advisors就是配置文件中所有的advice和advisor
    return advisors;
}

找到advisors并未結(jié)束皮仁,還要跟pointcut做匹配籍琳,看這些advisor符不符合表達(dá)式條件。它最終調(diào)用到Pointcut的match方法魂贬。這個方法嵌套的太深巩割,就不貼代碼了,核心思想就是判斷class和class對應(yīng)的method是否與pointcut表達(dá)式匹配付燥。

創(chuàng)建代理

經(jīng)過上面查找匹配后宣谈,確定當(dāng)前的bean確實(shí)需要代理,就調(diào)用createProxy方法键科。

  • 設(shè)置代理工廠
protected Object createProxy(Class<?> beanClass, 
        String beanName, Object[] specificInterceptors, TargetSource targetSource) {
    //創(chuàng)建代理工廠
    ProxyFactory proxyFactory = new ProxyFactory();
    // Copy our properties (proxyTargetClass etc) inherited from ProxyConfig.
    proxyFactory.copyFrom(this);

    //將beanClass上的接口設(shè)置到代理工廠
    evaluateProxyInterfaces(beanClass, proxyFactory);
        
    //設(shè)置Advisor到代理工廠
    Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
    for (Advisor advisor : advisors) {
        proxyFactory.addAdvisor(advisor);
    }
    //設(shè)置目標(biāo)對象
    proxyFactory.setTargetSource(targetSource);
    return proxyFactory.getProxy(this.proxyClassLoader);
}
  • 創(chuàng)建

創(chuàng)建代理分為JDK的代理和Cglib的代理闻丑,這里我們先關(guān)注JDK的代理。getProxy方法就調(diào)用到JdkDynamicAopProxy類的方法勋颖。這個類還實(shí)現(xiàn)了InvocationHandler接口嗦嗡,說明它同時還是調(diào)用處理程序。即在調(diào)用代理類的invoke方法時饭玲,實(shí)際上就會調(diào)用到JdkDynamicAopProxy.invoke()侥祭。

public Object getProxy(ClassLoader classLoader) {
    //這里又給代理工廠加了兩個接口 SpringProxy和Advised
    Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);
    
    //這個方法就比較熟悉了,正是JDK動態(tài)代理的方法
    //這里的this就是JdkDynamicAopProxy實(shí)例茄厘。
    return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}

2矮冬、代理類的調(diào)用

在實(shí)例化Bean和完成依賴注入后,會判斷當(dāng)前的Bean是否需要代理次哈,如果需要胎署,就生成代理類把原始類替換掉。在業(yè)務(wù)方法里面調(diào)用的時候窑滞,就會調(diào)用到JdkDynamicAopProxy.invoke()琼牧。

在執(zhí)行invoke的時候恢筝,我們又可以分為兩個步驟...-_-||,是不是跟二很有緣巨坊,每次都是兩個步驟撬槽。。

  1. 獲取方法的攔截鏈

首先從代理工廠中拿到所有的advisor抱究,然后判斷是不是PointcutAdvisor類型恢氯,其次先matches一下targetClass,再matches一下method鼓寺,證明這個類的方法在pointcut范圍內(nèi),加入interceptorList勋磕,最后返回妈候。

public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
            Advised config, Method method, Class<?> targetClass) {
    List<Object> interceptorList = new ArrayList<Object>(config.getAdvisors().length);
    //config就是代理工廠的實(shí)例
    for (Advisor advisor : config.getAdvisors()) {
        if (advisor instanceof PointcutAdvisor) {
            PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
            if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(targetClass)) {
                MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
                MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
                if (MethodMatchers.matches(mm, method, targetClass, hasIntroductions)) {
                    interceptorList.addAll(Arrays.asList(interceptors));
                }
            }
        }
    }
    return interceptorList;
}
  1. 調(diào)用

拿到方法的攔截鏈,然后調(diào)用挂滓。調(diào)用的時候有個地方比較有意思苦银。它先創(chuàng)建了一個對象ReflectiveMethodInvocation。這個對象有兩個參數(shù)

currentInterceptorIndex   //當(dāng)前調(diào)用的攔截器的索引,默認(rèn)值-1
interceptorsAndDynamicMethodMatchers   //攔截器的列表

然后看它的調(diào)用方法赶站。

public Object proceed() throws Throwable {
    
    //如果倆個變量相等幔虏,說明已經(jīng)調(diào)用完了所有的攔截器
    if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
        //執(zhí)行被代理方法
        return invokeJoinpoint();
    }
    //從-1開始,每次累加1贝椿。interceptorOrInterceptionAdvice就是對應(yīng)的每一個通知
    //比如before想括、after、after-returning...
    //對應(yīng)的實(shí)例類分別是:
    //MethodBeforeAdviceInterceptor烙博、AspectJAfterAdvice瑟蜈、AfterReturningAdviceInterceptor
    Object interceptorOrInterceptionAdvice =
            this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
            
    
    //在調(diào)用具體通知的invoke方法時,把當(dāng)前對象當(dāng)參數(shù)傳了過去渣窜。
    //因為在具體的通知執(zhí)行之后還要回調(diào)回來铺根,執(zhí)行當(dāng)前對象的proceed().
    //這樣就形成了一個通知調(diào)用鏈,當(dāng)所有的通知執(zhí)行完畢乔宿,調(diào)用上面的invokeJoinpoint()
    return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}

看完它的調(diào)用流程位迂,我們還有一個疑問。通知一共有5種類型详瑞,前置通知掂林、后置通知、方法返回后通知蛤虐、環(huán)繞通知党饮、異常通知。那么驳庭,它是怎么保證調(diào)用順序的呢刑顺?
我們來看兩個通知類里面具體的實(shí)現(xiàn)氯窍。

前置通知
public Object invoke(MethodInvocation mi) throws Throwable {
    //這個比較簡單,執(zhí)行完before就回調(diào)
    this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis() );
    return mi.proceed();
}
后置通知
public Object invoke(MethodInvocation mi) throws Throwable {
    //這個就比較有趣了蹲堂,它先執(zhí)行回調(diào)
    //通過finally機(jī)制再執(zhí)行自己的通知方法
    try {
        return mi.proceed();
    }
    finally {
        invokeAdviceMethod(getJoinPointMatch(), null, null);
    }
}
環(huán)繞通知
//環(huán)繞通知會調(diào)用到切面類的自定義方法
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("環(huán)繞通知之前");
    //可以自己判斷還要不要回調(diào)回去狼讨。
    //回調(diào)回去的話,接著循環(huán)調(diào)用鏈
    //如果不再回調(diào)柒竞,就要看鏈的順序了政供,在它之后的就不再執(zhí)行
    Object result = joinPoint.proceed();
    System.out.println("環(huán)繞通知之后");
    return result;
}

3、基于注解的AOP

如果想使用注解AOP朽基,需要開啟一個配置布隔。<aop:aspectj-autoproxy />。Spring解析配置標(biāo)簽的時候稼虎,調(diào)用到AspectJAutoProxyBeanDefinitionParser.parse()衅檀。
這個方法主要就干了一件事:注冊入口類AnnotationAwareAspectJAutoProxyCreator
XML配置方式的AOP霎俩,Spring注冊的入口類叫做AspectJAwareAdvisorAutoProxyCreator哀军。

它們的調(diào)用流程是一樣的,都是在實(shí)例化之后調(diào)用爺爺類的AbstractAutoProxyCreator.postProcessAfterInitialization()打却∩际剩回憶一下,wrapIfNecessary方法是真正產(chǎn)生代理的地方柳击。它先獲取所有的通知并與當(dāng)前的bean class匹配猿推,如果有,就說明當(dāng)前的Bean需要代理腻暮,則產(chǎn)生代理類彤守。我們知道,在XML配置方式的AOP中哭靖,Spring把配置的通知都封裝成advisor注冊到容器里具垫,所以在獲取的時候,直接在Bean工廠中匹配Advisor類型的Bean就行试幽。
但是筝蚕,在解析注解AOP的時候,我們看到它只是注冊了一個入口類而已呀铺坞,并沒有注冊advisor起宽,那么,在這里怎么獲取呢济榨?

目光回到查詢advisor的方法坯沪。

protected List<Advisor> findCandidateAdvisors() {
    // Add all the Spring advisors found according to superclass rules.
    // 這個是查詢XML配置方式的advoisor
    List<Advisor> advisors = super.findCandidateAdvisors();
    // Build Advisors for all AspectJ aspects in the bean factory.
    // 這個就是查詢注解方式的advisor
    advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
    return advisors;
}

buildAspectJAdvisors方法查詢advisor的時候,它大致可以分3個步驟擒滑。

  • 從Bean工廠獲取所有的beanName
    String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Object.class, true, false);

  • 循環(huán)beanNames腐晾,判斷bean是否包含Aspect注解

for (String beanName : beanNames) {
    Class<?> beanType = this.beanFactory.getType(beanName);
    if (this.advisorFactory.isAspect(beanType)) {
        aspectNames.add(beanName);
        ......
    }
}
  • 獲取Advisors叉弦。先拿到Class對象上的所有Method對象,根據(jù)Method對象的Annotation類型藻糖,返回不同的Advice對象淹冰。這個跟XML方式返回的Advice對象是一樣的。
switch (aspectJAnnotation.getAnnotationType()) {
    case AtBefore:
        springAdvice = new AspectJMethodBeforeAdvice(candidateAdviceMethod, ajexp, aif);
        break;
    case AtAfter:
        springAdvice = new AspectJAfterAdvice(candidateAdviceMethod, ajexp, aif);
        break;
    case AtAfterReturning:
        springAdvice = new AspectJAfterReturningAdvice(candidateAdviceMethod, ajexp, aif);
        AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation();
        if (StringUtils.hasText(afterReturningAnnotation.returning())) {
            springAdvice.setReturningName(afterReturningAnnotation.returning());
        }
        break;
    case AtAfterThrowing:
        springAdvice = new AspectJAfterThrowingAdvice(candidateAdviceMethod, ajexp, aif);
        AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation();
        if (StringUtils.hasText(afterThrowingAnnotation.throwing())) {
            springAdvice.setThrowingName(afterThrowingAnnotation.throwing());
        }
        break;
    case AtAround:
        springAdvice = new AspectJAroundAdvice(candidateAdviceMethod, ajexp, aif);
        break;
    case AtPointcut:
        if (logger.isDebugEnabled()) {
            logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() + "'");
        }
        return null;
    default:
        throw new UnsupportedOperationException(
                "Unsupported advice type on method " + candidateAdviceMethod);
}

最后將advice和pointcut封裝成InstantiationModelAwarePointcutAdvisorImpl對象返回巨柒。返回之后樱拴,創(chuàng)建代理類進(jìn)行替換。

由此看來洋满,注解方式的AOP晶乔,是在查詢Advisors的時候才去解析并生成的,其他的與XML的配置方式處理流程都是一樣的芦岂。

4瘪弓、 總結(jié)

本章節(jié)我們重點(diǎn)闡述了3個問題。即怎樣產(chǎn)生代理類禽最、代理類的執(zhí)行過程、AnnotationAOP的處理流程袱饭。結(jié)合本章節(jié)和上一章節(jié)的內(nèi)容川无,可能使我們對Spring AOP的內(nèi)部處理流程加深了印象。

  • 怎樣產(chǎn)生代理類虑乖?
    通過循環(huán)beanNames實(shí)例化Bean對象懦趋,判斷此對象是否與pointcut表達(dá)式匹配。如果匹配就根據(jù)advice生成不同的advisor對象疹味,然后調(diào)用JDK或者CGLIB的方法生成代理類返回仅叫。

  • 代理類執(zhí)行
    調(diào)用JDK或者CGLIB的invoke方法,查詢advisor的調(diào)用鏈糙捺。鏈?zhǔn)秸{(diào)用诫咱,根據(jù)通知類型調(diào)用不同的advice實(shí)現(xiàn)增強(qiáng)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末洪灯,一起剝皮案震驚了整個濱河市坎缭,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌签钩,老刑警劉巖掏呼,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異铅檩,居然都是意外死亡憎夷,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進(jìn)店門昧旨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拾给,“玉大人祥得,你說我怎么就攤上這事∶鳎” “怎么了啃沪?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長窄锅。 經(jīng)常有香客問我创千,道長,這世上最難降的妖魔是什么入偷? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任追驴,我火速辦了婚禮,結(jié)果婚禮上疏之,老公的妹妹穿的比我還像新娘殿雪。我一直安慰自己,他們只是感情好锋爪,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布丙曙。 她就那樣靜靜地躺著,像睡著了一般其骄。 火紅的嫁衣襯著肌膚如雪亏镰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天拯爽,我揣著相機(jī)與錄音索抓,去河邊找鬼。 笑死毯炮,一個胖子當(dāng)著我的面吹牛逼肯,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播桃煎,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼篮幢,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了备禀?” 一聲冷哼從身側(cè)響起洲拇,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎曲尸,沒想到半個月后赋续,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡另患,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年纽乱,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片昆箕。...
    茶點(diǎn)故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡鸦列,死狀恐怖租冠,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情薯嗤,我是刑警寧澤顽爹,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站骆姐,受9級特大地震影響镜粤,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜玻褪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一肉渴、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧带射,春花似錦同规、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至灿里,卻和暖如春朱灿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背钠四。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留跪楞,地道東北人缀去。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像甸祭,于是被迫代替她去往敵國和親缕碎。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評論 2 354

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