關于 Spring AOP的一些理解

事情要從一次奇怪的 bug 說起甜攀, 過程大約是這樣的捐寥, 在一次使用 @Transactional 注解的過程中胸墙, 為了分離真正需要事物的代碼和其他業(yè)務邏輯寡润, 我對代碼進行了抽取捆憎, 抽取后的代碼類似于下面這樣:

但是在進行測試時發(fā)現雖然加上了事物注解, 但是事物卻完全沒有生效梭纹。 當時可以說是百思不得其解躲惰, 只能猜測是因為 aop 是通過 jdk 動態(tài)代理方式實現的, 所以 commitSomething 方法并未聲明在接口上变抽, 自然也就不能被代理础拨, 也就不能實現事物。 為驗證這個猜測绍载, 修改 proxyTargetClass=true 切換為 cglib 形式代理诡宗, 我們知道, cglib 是通過子類化來實現的代理击儡, 具體生成的代碼類似于下面這樣:

// 父類代碼
public class AService {
  pubiic void consume() {
    // do something
  }
}
// 生成的代理子類
public class AService$EnhanceByCGLIB$$xxxxx extends AService {
    private MethodInterceptor CGLIB$CALLBACK_0;
    
    final void CGLIB$consume$0(){
        super.consume();
    }
    
    public final void consume() {
      MethodIterceptor methodIterceptor = CGLIB$CALLBACK_0;
      if (methodIterceptor == null) {
          CGLIB$BIND_CALLBACKS(this);
          methodIterceptor = CGLIB$CALLBACK_0;
      }
      if (this.CGLIB$CALLBACK_0 != null) {
           methodIterceptor.intercept(this, CGLIB$g$0$Method, CGLIB$emptyArgs, CGLIB$g$0$Proxy);
       }
       else {
           CGLIB$consume$0();
       }
  }
}

但是修改為 cglib代理方式后事物依然沒有生效塔沃,證明之前的猜測不太正確。為了弄清楚這個問題阳谍, 我做了一些研究蛀柴,并在這個過程中對 Spring 實現 Aop 的方式做了如下總結:
我們知道 AOP 是Aspect Oriented Programming 面向切面編程的簡稱,常被我們用作在事物矫夯, 日志鸽疾,安全,緩存等方面训貌。
而 Spring 也實現了 AOP 機制制肮,主要有兩種方式 :

  • 1.Proxy base
  • 2.AspectJ
    第一種是基于代理方式實現, 比如 JDK Dynamic Proxy 和 Cglib proxy
    第二種是基于 AspectJ 框架織入源代碼方式實現。
    下面我主要講下 Spring Proxy base AOP 方式的實現递沪。

代理類構成

Spring 默認有兩種形式代理 jdk dynamic proxy 和 cglib proxy 在Spring 中這兩種形式代理由 AopProxy 負責創(chuàng)建 豺鼻。AopProxy有兩個實現類 JdkDynamicAopProxy 和 CglibAopProxy 繼承關系如下圖:


選擇代理類

Spring 默認使用 jdk dynamic proxy 方式實現 AOP, 當被代理類無接口時 Spring 使用 cglib 方式實現代理。創(chuàng)建 AopProxy 邏輯主要在類DefaultAopProxyFactory中代碼如下:



可以看到判斷使用哪一種代理實現邏輯是根據 config.isProxyTargetClass 來判斷的区拳, 而這個 config 是一個AdvisedSupport 的實例拘领。 AdvisedSupport 是管理Spring AOP 配置的類, 類層次結構如下:



而 Spring 正是通過 ProxyFactory getProxy 來構造的代理樱调。
ProxyFactory 的 isProxyTargetClass 也會在真正創(chuàng)建代理對象時提前計算好约素。

創(chuàng)建并調用ProxyFacotry在類 AnnotationAwareAspectJAutoProxyCreator 中代碼如下:



可以看到 AnnotationAwareAspectJAutoProxyCreator 的 createProxy 主要做了兩件事,
  • 1.創(chuàng)建 ProxyFactory 并初始化 ProxyFactory 的一些構造代理相關的屬性
  • 2.創(chuàng)建代理 bean

代理類的調用與執(zhí)行

因為 JdkDynamicAopProxy就是基于 Java的動態(tài)代理機制相信大家都比較熟悉笆凌, 這里就略過不講圣猎。后文主要講下 CglibAopProxy 調用與執(zhí)行。
在前文中可以看到 Spring 是通過 AnnotationAwareAspectJAutoProxyCreator的createProxy方法來構造具有AOP能力的 bean乞而, 這個過程就是設置好代理的攔截器以及一些其他與構造代理相關屬性后將創(chuàng)建過程委托給 JdkDynamicAopProxy 或 CglibAopProxy 中具體一個送悔, 再由 JdkDynamicAopProxy 或 CglibAopProxy 調用 getProxy 創(chuàng)建代理 bean。 所以實際我們通過 @Autowired 注解或者從 Spring 上下文中拿到的 bean 就已經是生成好的代理 bean了爪模。 可以 debug 看下欠啤, 獲取的 bean 的 class 一般是 $Proxy 或 $EnhanceByCGLIB 這樣的后綴。
繼續(xù)看下代理攔截邏輯也就是 AOP 的切面邏輯是如何實現的屋灌, 在 CglibAopProxy 構造代理對象時代理攔截邏輯是通過如下代碼實現注冊:



而 Spring 主要是通過 DynamicAdvisedInterceptor 類實現攔洁段,截繼承關系如圖:



DynamicAdvisedInterceptor 的 intercept 實現了具體的攔截邏輯:

可以看到具體實現邏輯首先是獲取攔截器鏈, 再通過 CglibMethodInvocation 來執(zhí)行攔截器鏈, 具體再看 CglibMethodInvocation 的 invokeJoinpoint 方法共郭, 具體邏輯如下:

Spring 通過攔截器鏈對被調用方法進行動態(tài)匹配如果匹配上則進行連接器調用否則繼續(xù)往后面鏈路執(zhí)行當所有攔截器都被執(zhí)行后祠丝, 調用

methodProxy.invoke(target, arguments) 

執(zhí)行被代理方法。 至此 Spring Proxy base 代理整個流程基本梳理完畢除嘹。

總結

理解完spring 整個 aop 實現邏輯也就可以理解前文 bug, 其實前文中不能應用上事物問題写半,主要原因是內部方法調用引起的。 因為在方法內部尉咕, 作用域就不是 proxy 對象叠蝇, 而是 target source (被代理源對象), 所以理解了原因解決這個問題自然也就簡單了:
主要可以通過

  • 1.AopContext.currentProxy() 獲取代理對象
  • 2.從Spring 上下文中獲取代理對象
  • 3.使用 AspectJ 方式實現 Aop
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末年缎,一起剝皮案震驚了整個濱河市悔捶,隨后出現的幾起案子,更是在濱河造成了極大的恐慌晦款,老刑警劉巖炎功,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡壁晒,警方通過查閱死者的電腦和手機豹绪,發(fā)現死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來淤齐,“玉大人,你說我怎么就攤上這事袜匿「模” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵居灯,是天一觀的道長祭务。 經常有香客問我内狗,道長,這世上最難降的妖魔是什么义锥? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任柳沙,我火速辦了婚禮,結果婚禮上拌倍,老公的妹妹穿的比我還像新娘赂鲤。我一直安慰自己,他們只是感情好柱恤,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布数初。 她就那樣靜靜地躺著,像睡著了一般梗顺。 火紅的嫁衣襯著肌膚如雪泡孩。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天荚守,我揣著相機與錄音珍德,去河邊找鬼。 笑死矗漾,一個胖子當著我的面吹牛锈候,可吹牛的內容都是我干的。 我是一名探鬼主播敞贡,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼泵琳,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了誊役?” 一聲冷哼從身側響起获列,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蛔垢,沒想到半個月后击孩,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡鹏漆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年巩梢,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片艺玲。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡括蝠,死狀恐怖,靈堂內的尸體忽然破棺而出饭聚,到底是詐尸還是另有隱情忌警,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布秒梳,位于F島的核電站法绵,受9級特大地震影響箕速,放射性物質發(fā)生泄漏。R本人自食惡果不足惜礼烈,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一弧满、第九天 我趴在偏房一處隱蔽的房頂上張望婆跑。 院中可真熱鬧此熬,春花似錦、人聲如沸滑进。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽扶关。三九已至阴汇,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間节槐,已是汗流浹背搀庶。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留铜异,地道東北人哥倔。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像揍庄,于是被迫代替她去往敵國和親咆蒿。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內容