向您生動(dòng)地講解Spring AOP 源碼(3)

前言

往期文章:

在上一章向您生動(dòng)地講解Spring AOP 源碼(2)中,作者介紹了【如何獲取對(duì)應(yīng) Bean 適配的Advisors 鏈】娄涩。

在本章中寂拆,作者會(huì)向您介紹埠况,Spring AOP 是如何解析我們配置的Aspect,并將advice織入的兄一?

在本章的附錄部分,還會(huì)介紹如何保存 JDK動(dòng)態(tài)代理和 Cglib生成的類文件。

閑話不多說捷兰,讓我們直接開始。

創(chuàng)建代理類

上一章結(jié)束之后负敏,Spring AOP的核心邏輯已經(jīng)走了一半了贡茅,獲取了目標(biāo)類所適用的增強(qiáng)器列表,下面開始分析獲取代理的過程其做。

未免讀者閱讀不連貫顶考,我們?cè)儋N一下向您生動(dòng)地講解Spring AOP 源碼(1)中我們最后講解的一段源碼,由此繼續(xù)往下講述妖泄。

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

wrapIfNecessary

TODO-2 createProxy

image.png

稍微提一下 TargetSource 這個(gè)概念驹沿,它用于封裝真實(shí)實(shí)現(xiàn)類的信息,在我理解看來就是把獲取目標(biāo)對(duì)象這個(gè)步驟做了一個(gè)代理的操作蹈胡,提供一個(gè)擴(kuò)展點(diǎn)給外部渊季,使得使用者可以通過這個(gè)擴(kuò)展點(diǎn)去對(duì)目標(biāo)對(duì)象做一些處理;

上面用了 SingletonTargetSource 這個(gè)實(shí)現(xiàn)類罚渐,其實(shí)我們這里也不太需要關(guān)心這個(gè)却汉,知道有這么回事就可以了,個(gè)人感覺這個(gè)擴(kuò)展點(diǎn)用處不是特別的大荷并。


來?xiàng)l分割線合砂,正式進(jìn)入今天的核心內(nèi)容。

現(xiàn)在源织,讓我們開始解析翩伪,Spring AOP創(chuàng)建代理類的流程。

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

image

流程:

  1. 獲取當(dāng)前類中的屬性
  2. 添加代理接口
  3. 封裝Advisor并加入到ProxyFactory中
  4. 設(shè)置要代理的類
  5. Spring中為子類提供了定制的函數(shù)customizeProxyFactory谈息,子類可以在此函數(shù)中對(duì)ProxyFactory的進(jìn)一步封裝
  6. ★★★ 獲取代理操作

主要分析關(guān)鍵的生成代理類的操作缘屹。

源碼位置:ProxyFactory#getProxy(..)

    public Object getProxy(ClassLoader classLoader) {
        return createAopProxy().getProxy(classLoader);
    }

這里要分為兩步,

  1. 創(chuàng)建AopProxy
  2. 獲取代理類

1. 創(chuàng)建 AopProxy

    protected final synchronized AopProxy createAopProxy() {
        if (!this.active) {
            activate();
        }
        return getAopProxyFactory().createAopProxy(this);
    }

這一步之后我們根據(jù)ProxyConfig 獲取到了對(duì)應(yīng)的AopProxy的實(shí)現(xiàn)類黎茎,分別是JdkDynamicAopProxyObjenesisCglibAopProxy囊颅。

image.png

2. 獲取代理類

JDK 動(dòng)態(tài)代理

源碼位置:JdkDynamicAopProxy#getProxy(..)

我們關(guān)注的是最后一行代碼Proxy.newProxyInstance(classLoader, proxiedInterfaces, this)

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)  {
        ...
    }

注:到這里傅瞻,你需要了解一下JDK動(dòng)態(tài)代理的使用知識(shí)踢代,如果能了解原理,那就更好了

第一個(gè)參數(shù)是類加載器嗅骄,第二個(gè)參數(shù)是目標(biāo)類的接口集合胳挎,第三個(gè)參數(shù)則是InvocationHandler的實(shí)現(xiàn)類,JdkDynamicAopProxy在創(chuàng)建代理的時(shí)候溺森,是將自身作為 InvocationHandler傳入的慕爬,由此可知JdkDynamicAopProxy本身實(shí)現(xiàn)了InvocationHandler接口窑眯。

熟悉JDK動(dòng)態(tài)代理實(shí)現(xiàn)機(jī)制的同學(xué)應(yīng)該會(huì)知道,調(diào)用代理類的對(duì)應(yīng)方法時(shí)医窿,代理類實(shí)際上是通過invoke(Object proxy, Method method, Object[] args)方法來完成 target class 方法的調(diào)用磅甩,并在里面進(jìn)行一些代理類想做的其他的操作。

在AOP中姥卢,invoke方法會(huì)完成AOP編織實(shí)現(xiàn)的封裝卷要。所以讓我們看看這個(gè)invoke方法是怎么實(shí)現(xiàn)的。

image

invoke方法的關(guān)鍵就在于独榴,利用責(zé)任鏈模式僧叉,遞歸調(diào)用的方法,來完成advice 的織入棺榔。

ReflectiveMethodInvocation構(gòu)造方法

關(guān)鍵的ReflectiveMethodInvocation#proceed()方法

我們來看看((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this)瓶堕,這個(gè)方法有多種實(shí)現(xiàn),其中一些我們熟悉的或者說需要關(guān)注的實(shí)現(xiàn)症歇,對(duì)應(yīng)的就是我們Advice的類型郎笆,或者說增強(qiáng)的時(shí)機(jī)。

術(shù)語(yǔ) 概念
Before 在方法被調(diào)用之前執(zhí)行增強(qiáng)
After 在方法被調(diào)用之后執(zhí)行增強(qiáng)
After-returning 在方法成功執(zhí)行之后執(zhí)行增強(qiáng)
After-throwing 在方法拋出指定異常后執(zhí)行增強(qiáng)
Around 在方法調(diào)用的前后執(zhí)行自定義的增強(qiáng)行為(最靈活的方式)

這里我們用概覽的方式過一下這幾種的實(shí)現(xiàn)忘晤,

MethodBeforeAdviceInterceptor#invoke(..)

AspectJAfterAdvice#invoke(..)

AfterReturningAdviceInterceptor#invoke(..)

AspectJAfterThrowingAdvice#invoke(..)

AspectJAroundAdvice#invoke(..)

Cglib 代理

Cglib 代理 和 JDK 代理 在流程上相似题画,只是在具體實(shí)現(xiàn)上不一樣。核心就是Enhancer和獲得callbacks的過程德频。這里就不分析了。

小結(jié)

本章的核心內(nèi)容就是缩幸,創(chuàng)建代理類時(shí)壹置,Spring 根據(jù) AOP 配置選擇JDK動(dòng)態(tài)代理或是 Cglib 代理,增強(qiáng)器的織入是按照事先排序好的順序表谊、advice 的類型來起作用的钞护。

個(gè)人認(rèn)為核心難點(diǎn)還是在對(duì)JDK動(dòng)態(tài)代理和Cglib代理 原理的理解。讀者如果對(duì)這塊不熟悉爆办,可以查閱其他的文章進(jìn)行學(xué)習(xí)难咕。

可以學(xué)習(xí)到責(zé)任鏈的設(shè)計(jì)模式、JDK 動(dòng)態(tài)代理和反射距辆、Cglib代理等Java 核心知識(shí)余佃。

最后,作者寫到這里跨算,也是長(zhǎng)呼一口氣爆土,源碼分析不像新技術(shù)那樣,一開始就抓人眼球诸蚕,很難寫得引人入勝步势,通常篇幅過長(zhǎng)氧猬,寫的人會(huì)乏,看的人也會(huì)乏坏瘩。所幸作者堅(jiān)持了下來盅抚,在這期間對(duì)AOP的源碼也有了更深的理解。

附錄

理解JDK 動(dòng)態(tài)代理 和 CGLIB 代理 生成的代理類的源碼會(huì)讓你對(duì)advice織入的時(shí)機(jī)有更深的理解倔矾。

JDK 動(dòng)態(tài)代理 類源碼

TestSvc

public interface TestSvc {
    void process();
}
@Service("testSvc")
public class TestSvcImpl implements TestSvc {

    @Override
    public void process() {
        System.out.println("test svc is working");
    }
}

生成代理類:

關(guān)鍵點(diǎn):實(shí)現(xiàn)接口妄均,method.invoke(..) 反射調(diào)用

package com.sun.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import ric.study.demo.aop.svc.TestSvc;

public final class $Proxy19
  extends Proxy
  implements TestSvc
{
  private static Method m1;
  private static Method m2;
  private static Method m0;
  private static Method m3;
  
  public $Proxy19(InvocationHandler paramInvocationHandler)
  {
    super(paramInvocationHandler);
  }
  
  public final boolean equals(Object paramObject)
  {
    try
    {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  public final String toString()
  {
    try
    {
      return (String)this.h.invoke(this, m2, null);
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  public final int hashCode()
  {
    try
    {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  public final void process()
  {
    try
    {
      this.h.invoke(this, m3, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  static
  {
    try
    {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      m3 = Class.forName("ric.study.demo.aop.svc.TestSvc").getMethod("process", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
}

如何保存JDK 動(dòng)態(tài)代理的源文件

只需要在系統(tǒng)變量中設(shè)置sun.misc.ProxyGenerator.saveGeneratedFilestrue即可。比如這樣破讨,

image.png

會(huì)在項(xiàng)目目錄下生成com.sun.proxy目錄丛晦,并存儲(chǔ)對(duì)應(yīng)的文件。想要找到你的代理類到底是哪個(gè)提陶,你還需要打印出(或者debug查看)這個(gè)代理類的類名烫沙,像我上圖一樣。

image.png

Cglib 代理 類源碼

關(guān)鍵:繼承隙笆;MethodInterceptor.intercept()锌蓄;

BTW:Cglib 的 源碼未免太過冗長(zhǎng),放上來的閱讀體驗(yàn)非常不好(1000+行)撑柔。讀者可以按照我后面提到的方法自己生成瘸爽,然后利用反編譯工具查看。

如何保存 Cglib 生成代理類 的源文件

和JDK 動(dòng)態(tài)代理類似铅忿,System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "...");剪决,設(shè)置class 文件的輸出目錄即可。

image.png

如果本文有幫助到你檀训,希望能點(diǎn)個(gè)贊柑潦,這是對(duì)我的最大動(dòng)力。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末峻凫,一起剝皮案震驚了整個(gè)濱河市渗鬼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌荧琼,老刑警劉巖譬胎,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異命锄,居然都是意外死亡堰乔,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門累舷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來浩考,“玉大人,你說我怎么就攤上這事被盈∥瞿酰” “怎么了搭伤?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)袜瞬。 經(jīng)常有香客問我怜俐,道長(zhǎng),這世上最難降的妖魔是什么邓尤? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任拍鲤,我火速辦了婚禮,結(jié)果婚禮上汞扎,老公的妹妹穿的比我還像新娘季稳。我一直安慰自己,他們只是感情好澈魄,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布景鼠。 她就那樣靜靜地躺著,像睡著了一般痹扇。 火紅的嫁衣襯著肌膚如雪铛漓。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天鲫构,我揣著相機(jī)與錄音浓恶,去河邊找鬼。 笑死结笨,一個(gè)胖子當(dāng)著我的面吹牛包晰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播炕吸,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼杜窄,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了算途?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤蚀腿,失蹤者是張志新(化名)和其女友劉穎嘴瓤,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體莉钙,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡廓脆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了磁玉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片停忿。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蚊伞,靈堂內(nèi)的尸體忽然破棺而出席赂,到底是詐尸還是另有隱情吮铭,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布颅停,位于F島的核電站谓晌,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏癞揉。R本人自食惡果不足惜纸肉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望喊熟。 院中可真熱鬧柏肪,春花似錦、人聲如沸芥牌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)胳泉。三九已至拐叉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間扇商,已是汗流浹背凤瘦。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留案铺,地道東北人蔬芥。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像控汉,于是被迫代替她去往敵國(guó)和親笔诵。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

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