国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Spring AOP 源碼分析系列文章導讀

張春雷 / 836人閱讀

摘要:在寫完容器源碼分析系列文章中的最后一篇后,沒敢懈怠,趁熱打鐵,花了天時間閱讀了方面的源碼。從今天開始,我將對部分的源碼分析系列文章進行更新。全稱是,即面向切面的編程,是一種開發理念。在中,切面只是一個概念,并沒有一個具體的接口或類與此對應。

1. 簡介

前一段時間,我學習了 Spring IOC 容器方面的源碼,并寫了數篇文章對此進行講解。在寫完 Spring IOC 容器源碼分析系列文章中的最后一篇后,沒敢懈怠,趁熱打鐵,花了3天時間閱讀了 AOP 方面的源碼。開始以為 AOP 部分的源碼也會比較復雜,所以原計劃投入一周的時間用于閱讀源碼。但在我大致理清 AOP 源碼邏輯后,發現沒想的那么復雜,所以目前進度算是超前了。從今天(5.15)開始,我將對 AOP 部分的源碼分析系列文章進行更新。包括本篇文章在內,本系列大概會有4篇文章,我將會在接下來一周時間內陸續進行更新。在本系列文章中,我將會分析 Spring AOP 是如何為 bean 篩選合適的通知器(Advisor),以及代理對象生成的過程。除此之外,還會對攔截器的調用過程進行分析。與前面的文章一樣,本系列文章不會對 AOP 的 XML 配置解析過程進行分析。

下面來講講本篇文章的內容,在本篇文章中,我將會向大家介紹一下 AOP 的原理,以及 AOP 中的一些術語及其對應的源碼。我覺得,大家在閱讀 AOP 源碼時,一定要弄懂這些術語和源碼。不然,在閱讀 AOP 源碼的過程中,可能會有點暈。好了,其他的就不多說了,下面進入正題吧。

2. AOP 原理

關于 AOP 的原理,想必大家都知道了。無非是通過代理模式為目標對象生產代理對象,并將橫切邏輯插入到目標方法執行的前后。這樣一說,本章確實沒什么好說的了,畢竟原理就是這么簡單。不過原理歸原理,在具體的實現上,很多事情并沒想象的那么簡單。比如,我們需要確定是否應該為某個 bean 生成代理,如果應該的話,還要進一步確定將橫切邏輯插入到哪些方法上。說到橫切邏輯,這里簡單介紹一下。橫切邏輯其實就是通知(Advice),Spring 提供了5種通知,Spring 需要為每種通知提供相應的實現類。除了以上說的這些,在具體的實現過程中,還要考慮如何將 AOP 和 IOC 整合在一起,畢竟 IOC 是 Spring 框架的根基。除此之外,還有其他一些需要考慮的地方,這里就不一一列舉了。總之 AOP 原理說起來容易,但做起來卻不簡單,尤其是實現一個業界認可的,久經考驗的框架。所以,在隨后的文章中,讓我們帶著對代碼的敬畏之心,去學習 Spring AOP 模塊的源碼吧。

3. AOP 術語及相應的實現

本章我來向大家介紹一下 AOP 中的一些術語,并會把這些術語對應的代碼也貼出來。在介紹這些術語之前,我們先來了解一下 AOP 吧。AOP 全稱是 Aspect Oriented Programming,即面向切面的編程,AOP 是一種開發理念。通過 AOP,我們可以把一些非業務邏輯的代碼,比如安全檢查,監控等代碼從業務方法中抽取出來,以非侵入的方式與原方法進行協同。這樣可以使原方法更專注于業務邏輯,代碼結構會更加清晰,便于維護。

這里特別說明一下,AOP 并非是 Spring 獨創,AOP 有自己的標準,也有機構在維護這個標準。Spring AOP 目前也遵循相關標準,所以別認為 AOP 是 Spring 獨創的。

3.1 連接點 - Joinpoint

連接點是指程序執行過程中的一些點,比如方法調用,異常處理等。在 Spring AOP 中,僅支持方法級別的連接點。上面是比較官方的說明,下面舉個例子說明一下。現在我們有一個用戶服務 UserService 接口,該接口定義如下:

public interface UserService {
    void save(User user);
    void update(User user);
    void delete(String userId);
    User findOne(String userId);
    List findAll();
    boolean exists(String userId);
}

該接口的實現類是 UserServiceImpl,假設該類的方法調用如下:

如上所示,每個方法調用都是一個連接點。接下來,我們來看看連接點的定義:

public interface Joinpoint {

    /** 用于執行攔截器鏈中的下一個攔截器邏輯 */
    Object proceed() throws Throwable;

    Object getThis();

    AccessibleObject getStaticPart();

}

這個 Joinpoint 接口中,proceed 方法是核心,該方法用于執行攔截器邏輯。關于攔截器這里簡單說一下吧,以前置通知攔截器為例。在執行目標方法前,該攔截器首先會執行前置通知邏輯,如果攔截器鏈中還有其他的攔截器,則繼續調用下一個攔截器邏輯。直到攔截器鏈中沒有其他的攔截器后,再去調用目標方法。關于攔截器這里先說這么多,在后續文章中,我會進行更為詳細的說明。

上面說到一個方法調用就是一個連接點,那下面我們不妨看一下方法調用這個接口的定義。如下:

public interface Invocation extends Joinpoint {
    Object[] getArguments();
}

public interface MethodInvocation extends Invocation {
    Method getMethod();
}

如上所示,方法調用接口 MethodInvocation 繼承自 Invocation,Invocation 接口又繼承自 Joinpoint。看了上面的代碼,我想大家現在對連接點應該有更多的一些認識了。接下面,我們來繼續看一下 Joinpoint 接口的一個實現類 ReflectiveMethodInvocation。當然不是看源碼,而是看它的繼承體系圖。如下:

關于連接點的相關知識,我們先了解到這里。有了這些連接點,接下來要做的事情是對我們感興趣連接點進行一些橫切操作。在操作之前,我們首先要把我們所感興趣的連接點選中,怎么選中的呢?這就是切點 Pointcut 要做的事情了,繼續往下看。

3.2 切點 - Pointcut

剛剛說到切點是用于選擇連接點的,那么應該怎么選呢?在回答這個問題前,我們不妨先去看看 Pointcut 接口的定義。如下:

public interface Pointcut {

    /** 返回一個類型過濾器 */
    ClassFilter getClassFilter();

    /** 返回一個方法匹配器 */
    MethodMatcher getMethodMatcher();

    Pointcut TRUE = TruePointcut.INSTANCE;
}

Pointcut 接口中定義了兩個接口,分別用于返回類型過濾器和方法匹配器。下面我們再來看一下類型過濾器和方法匹配器接口的定義:

public interface ClassFilter {
    boolean matches(Class clazz);
    ClassFilter TRUE = TrueClassFilter.INSTANCE;

}

public interface MethodMatcher {
    boolean matches(Method method, Class targetClass);
    boolean matches(Method method, Class targetClass, Object... args);
    boolean isRuntime();
    MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
}

上面的兩個接口均定義了 matches 方法,用戶只要實現了 matches 方法,即可對連接點進行選擇。在日常使用中,大家通常是用 AspectJ 表達式對連接點進行選擇。Spring 中提供了一個 AspectJ 表達式切點類 - AspectJExpressionPointcut,下面我們來看一下這個類的繼承體系圖:

如上所示,這個類最終實現了 Pointcut、ClassFilter 和 MethodMatcher 接口,因此該類具備了通過 AspectJ 表達式對連接點進行選擇的能力。那下面我們不妨寫一個表達式對上一節的連接點進行選擇,比如下面這個表達式:

execution(* *.find*(..))

該表達式用于選擇以 find 的開頭的方法,選擇結果如下:

通過上面的表達式,我們可以就可以選中 findOne 和 findAll 兩個方法了。那選中方法之后呢?當然是要搞點事情。so,接下來通知(Advice)就該上場了。

3.3 通知 - Advice

通知 Advice 即我們定義的橫切邏輯,比如我們可以定義一個用于監控方法性能的通知,也可以定義一個安全檢查的通知等。如果說切點解決了通知在哪里調用的問題,那么現在還需要考慮了一個問題,即通知在何時被調用?是在目標方法前被調用,還是在目標方法返回后被調用,還在兩者兼備呢?Spring 幫我們解答了這個問題,Spring 中定義了以下幾種通知類型:

前置通知(Before advice)- 在目標方便調用前執行通知

后置通知(After advice)- 在目標方法完成后執行通知

返回通知(After returning advice)- 在目標方法執行成功后,調用通知

異常通知(After throwing advice)- 在目標方法拋出異常后,執行通知

環繞通知(Around advice)- 在目標方法調用前后均可執行自定義邏輯

上面是對通知的一些介紹,下面我們來看一下通知的源碼吧。如下:

public interface Advice {

}

如上,通知接口里好像什么都沒定義。不過別慌,我們再去到它的子類接口中一探究竟。

/** BeforeAdvice */
public interface BeforeAdvice extends Advice {

}

public interface MethodBeforeAdvice extends BeforeAdvice {

    void before(Method method, Object[] args, Object target) throws Throwable;
}

/** AfterAdvice */
public interface AfterAdvice extends Advice {

}

public interface AfterReturningAdvice extends AfterAdvice {

    void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable;
}

從上面的代碼中可以看出,Advice 接口的子類接口里還是定義了一些東西的。下面我們再來看看 Advice 接口的具體實現類 AspectJMethodBeforeAdvice 的繼承體系圖,如下:

現在我們有了切點 Pointcut 和通知 Advice,由于這兩個模塊目前還是分離的,我們需要把它們整合在一起。這樣切點就可以為通知進行導航,然后由通知邏輯實施精確打擊。那怎么整合兩個模塊呢?答案是,切面。好的,是時候來介紹切面 Aspect 這個概念了。

3.4 切面 - Aspect

切面 Aspect 整合了切點和通知兩個模塊,切點解決了 where 問題,通知解決了 when 和 how 問題。切面把兩者整合起來,就可以解決 對什么方法(where)在何時(when - 前置還是后置,或者環繞)執行什么樣的橫切邏輯(how)的三連發問題。在 AOP 中,切面只是一個概念,并沒有一個具體的接口或類與此對應。不過 Spring 中倒是有一個接口的用途和切面很像,我們不妨了解一下,這個接口就是切點通知器 PointcutAdvisor。我們先來看看這個接口的定義,如下:

public interface Advisor {

    Advice getAdvice();
    boolean isPerInstance();
}

public interface PointcutAdvisor extends Advisor {

    Pointcut getPointcut();
}

簡單來說一下 PointcutAdvisor 及其父接口 Advisor,Advisor 中有一個 getAdvice 方法,用于返回通知。PointcutAdvisor 在 Advisor 基礎上,新增了 getPointcut 方法,用于返回切點對象。因此 PointcutAdvisor 的實現類即可以返回切點,也可以返回通知,所以說 PointcutAdvisor 和切面的功能相似。不過他們之間還是有一些差異的,比如看下面的配置:


    

    
        
        

        
        
        
    

如上,一個切面中配置了一個切點和兩個通知,兩個通知均引用了同一個切點,即 pointcut-ref="helloPointcut"。這里在一個切面中,一個切點對應多個通知,是一對多的關系(可以配置多個 pointcut,形成多對多的關系)。而在 PointcutAdvisor 的實現類中,切點和通知是一一對應的關系。上面的通知最終會被轉換成兩個 PointcutAdvisor,這里我把源碼調試的結果貼在下面:

在本節的最后,我們再來看看 PointcutAdvisor 的實現類 AspectJPointcutAdvisor 的繼承體系圖。如下:

3.5 織入 - Weaving

現在我們有了連接點、切點、通知,以及切面等,可謂萬事俱備,但是還差了一股東風。這股東風是什么呢?沒錯,就是織入。所謂織入就是在切點的引導下,將通知邏輯插入到方法調用上,使得我們的通知邏輯在方法調用時得以執行。說完織入的概念,現在來說說 Spring 是通過何種方式將通知織入到目標方法上的。先來說說以何種方式進行織入,這個方式就是通過實現后置處理器 BeanPostProcessor 接口。該接口是 Spring 提供的一個拓展接口,通過實現該接口,用戶可在 bean 初始化前后做一些自定義操作。那 Spring 是在何時進行織入操作的呢?答案是在 bean 初始化完成后,即 bean 執行完初始化方法(init-method)。Spring通過切點對 bean 類中的方法進行匹配。若匹配成功,則會為該 bean 生成代理對象,并將代理對象返回給容器。容器向后置處理器輸入 bean 對象,得到 bean 對象的代理,這樣就完成了織入過程。關于后置處理器的細節,這里就不多說了.大家若有興趣,可以參考我之前寫的Spring IOC 容器源碼分析系列文章。

4.總結

本篇文章作為 AOP 源碼分析系列文章的導讀,簡單介紹了 AOP 中的一些術語,及其對應的源碼。總的來說,沒有什么特別之處。畢竟對于 AOP,大家都有所了解。因此,若文中有不妥錯誤之處,還請大家指明。當然,也希望多多指教。

好了,本篇文章先到這里。感謝大家的閱讀。

參考

Spring Framework Reference Documentation

《Spring 實戰》第4版 - Craig Walls

本文在知識共享許可協議 4.0 下發布,轉載需在明顯位置處注明出處
作者:coolblog.xyz
本文同步發布在我的個人博客:http://www.coolblog.xyz
附錄:Spring 源碼分析文章列表 Ⅰ. IOC
更新時間 標題
2018-05-30 Spring IOC 容器源碼分析系列文章導讀
2018-06-01 Spring IOC 容器源碼分析 - 獲取單例 bean
2018-06-04 Spring IOC 容器源碼分析 - 創建單例 bean 的過程
2018-06-06 Spring IOC 容器源碼分析 - 創建原始 bean 對象
2018-06-08 Spring IOC 容器源碼分析 - 循環依賴的解決辦法
2018-06-11 Spring IOC 容器源碼分析 - 填充屬性到 bean 原始對象
2018-06-11 Spring IOC 容器源碼分析 - 余下的初始化工作
Ⅱ. AOP
更新時間 標題
2018-06-17 Spring AOP 源碼分析系列文章導讀
2018-06-20 Spring AOP 源碼分析 - 篩選合適的通知器
2018-06-20 Spring AOP 源碼分析 - 創建代理對象
2018-06-22 Spring AOP 源碼分析 - 攔截器鏈的執行過程
Ⅲ. MVC
更新時間 標題
2018-06-29 Spring MVC 原理探秘 - 一個請求的旅行過程
2018-06-30 Spring MVC 原理探秘 - 容器的創建過程


本作品采用知識共享署名-非商業性使用-禁止演繹 4.0 國際許可協議進行許可。

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/71237.html

相關文章

  • Spring IOC 容器源碼分析 - 余下的初始化工作

    摘要:簡介本篇文章是容器源碼分析系列文章的最后一篇文章,本篇文章所分析的對象是方法,該方法用于對已完成屬性填充的做最后的初始化工作。后置處理器是拓展點之一,通過實現后置處理器接口,我們就可以插手的初始化過程。 1. 簡介 本篇文章是Spring IOC 容器源碼分析系列文章的最后一篇文章,本篇文章所分析的對象是 initializeBean 方法,該方法用于對已完成屬性填充的 bean 做最...

    Alfred 評論0 收藏0
  • Spring IOC 容器源碼分析系列文章導讀

    摘要:本文是容器源碼分析系列文章的第一篇文章,將會著重介紹的一些使用方法和特性,為后續的源碼分析文章做鋪墊。我們可以通過這兩個別名獲取到這個實例,比如下面的測試代碼測試結果如下本小節,我們來了解一下這個特性。 1. 簡介 Spring 是一個輕量級的企業級應用開發框架,于 2004 年由 Rod Johnson 發布了 1.0 版本。經過十幾年的迭代,現在的 Spring 框架已經非常成熟了...

    NSFish 評論0 收藏0
  • Spring IOC 容器源碼分析 - 創建單例 bean 的過程

    摘要:關于創建實例的過程,我將會分幾篇文章進行分析。源碼分析創建實例的入口在正式分析方法前,我們先來看看方法是在哪里被調用的。時,表明方法不存在,此時拋出異常。該變量用于表示是否提前暴露單例,用于解決循環依賴。 1. 簡介 在上一篇文章中,我比較詳細的分析了獲取 bean 的方法,也就是getBean(String)的實現邏輯。對于已實例化好的單例 bean,getBean(String) ...

    mochixuan 評論0 收藏0
  • Spring IOC 容器源碼分析 - 獲取單例 bean

    摘要:簡介為了寫容器源碼分析系列的文章,我特地寫了一篇容器的導讀文章。在做完必要的準備工作后,從本文開始,正式開始進入源碼分析的階段。從緩存中獲取單例。返回以上就是和兩個方法的分析。 1. 簡介 為了寫 Spring IOC 容器源碼分析系列的文章,我特地寫了一篇 Spring IOC 容器的導讀文章。在導讀一文中,我介紹了 Spring 的一些特性以及閱讀 Spring 源碼的一些建議。在...

    lufficc 評論0 收藏0
  • Spring IOC 容器源碼分析 - 循環依賴的解決辦法

    摘要:實例化時,發現又依賴于。一些緩存的介紹在進行源碼分析前,我們先來看一組緩存的定義。可是看完源碼后,我們似乎仍然不知道這些源碼是如何解決循環依賴問題的。 1. 簡介 本文,我們來看一下 Spring 是如何解決循環依賴問題的。在本篇文章中,我會首先向大家介紹一下什么是循環依賴。然后,進入源碼分析階段。為了更好的說明 Spring 解決循環依賴的辦法,我將會從獲取 bean 的方法getB...

    aikin 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<