前一段時間,我學習了 Spring IOC 容器方面的源碼,並寫了數篇文章對此進行講解。在寫完 Spring IOC 容器源碼分析系列文章中的最後一篇後,沒敢懈怠,趁熱打鐵,花了3天時間閱讀了 AOP 方面的源碼。開始覺得 AOP 部分的源碼也會比較複雜,因此原計劃投入一週的時間用於閱讀源碼。但在我大體理清 AOP 源碼邏輯後,發現沒想的那麼複雜,因此目前進度算是超前了。從今天(5.15)開始,我將對 AOP 部分的源碼分析系列文章進行更新。包括本篇文章在內,本系列大概會有4篇文章,我將會在接下來一週時間內陸續進行更新。在本系列文章中,我將會分析 Spring AOP 是如何爲 bean 篩選合適的通知器(Advisor),以及代理對象生成的過程。除此以外,還會對攔截器的調用過程進行分析。與前面的文章同樣,本系列文章不會對 AOP 的 XML 配置解析過程進行分析。html
下面來說講本篇文章的內容,在本篇文章中,我將會向你們介紹一下 AOP 的原理,以及 AOP 中的一些術語及其對應的源碼。我以爲,你們在閱讀 AOP 源碼時,必定要弄懂這些術語和源碼。否則,在閱讀 AOP 源碼的過程當中,可能會有點暈。好了,其餘的就很少說了,下面進入正題吧。java
關於 AOP 的原理,想必你們都知道了。無非是經過代理模式爲目標對象生產代理對象,並將橫切邏輯插入到目標方法執行的先後。這樣一說,本章確實沒什麼好說的了,畢竟原理就是這麼簡單。不過原理歸原理,在具體的實現上,不少事情並沒想象的那麼簡單。好比,咱們須要肯定是否應該爲某個 bean 生成代理,若是應該的話,還要進一步肯定將橫切邏輯插入到哪些方法上。說到橫切邏輯,這裏簡單介紹一下。橫切邏輯其實就是通知(Advice),Spring 提供了5種通知,Spring 須要爲每種通知提供相應的實現類。除了以上說的這些,在具體的實現過程當中,還要考慮如何將 AOP 和 IOC 整合在一塊兒,畢竟 IOC 是 Spring 框架的根基。除此以外,還有其餘一些須要考慮的地方,這裏就不一一列舉了。總之 AOP 原理提及來容易,但作起來卻不簡單,尤爲是實現一個業界承認的,久經考驗的框架。因此,在隨後的文章中,讓咱們帶着對代碼的敬畏之心,去學習 Spring AOP 模塊的源碼吧。spring
本章我來向你們介紹一下 AOP 中的一些術語,並會把這些術語對應的代碼也貼出來。在介紹這些術語以前,咱們先來了解一下 AOP 吧。AOP 全稱是 Aspect Oriented Programming,即面向切面的編程,AOP 是一種開發理念。經過 AOP,咱們能夠把一些非業務邏輯的代碼,好比安全檢查,監控等代碼從業務方法中抽取出來,以非侵入的方式與原方法進行協同。這樣可使原方法更專一於業務邏輯,代碼結構會更加清晰,便於維護。express
這裏特別說明一下,AOP 並不是是 Spring 首創,AOP 有本身的標準,也有機構在維護這個標準。Spring AOP 目前也遵循相關標準,因此別認爲 AOP 是 Spring 首創的。編程
鏈接點是指程序執行過程當中的一些點,好比方法調用,異常處理等。在 Spring AOP 中,僅支持方法級別的鏈接點。上面是比較官方的說明,下面舉個例子說明一下。如今咱們有一個用戶服務 UserService 接口,該接口定義以下:安全
public interface UserService { void save(User user); void update(User user); void delete(String userId); User findOne(String userId); List<User> 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 要作的事情了,繼續往下看。
剛剛說到切點是用於選擇鏈接點的,那麼應該怎麼選呢?在回答這個問題前,咱們不妨先去看看 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)
就該上場了。
通知 Advice 即咱們定義的橫切邏輯,好比咱們能夠定義一個用於監控方法性能的通知,也能夠定義一個安全檢查的通知等。若是說切點解決了通知在哪裏調用的問題,那麼如今還須要考慮了一個問題,即通知在什麼時候被調用?是在目標方法前被調用,仍是在目標方法返回後被調用,還在二者兼備呢?Spring 幫咱們解答了這個問題,Spring 中定義瞭如下幾種通知類型:
上面是對通知的一些介紹,下面咱們來看一下通知的源碼吧。以下:
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 這個概念了。
切面 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 和切面的功能類似。不過他們之間仍是有一些差別的,好比看下面的配置:
<bean id="aopCode" class="xyz.coolblog.aop.AopCode"/> <aop:config expose-proxy="true"> <aop:aspect ref="aopCode"> <!-- pointcut --> <aop:pointcut id="helloPointcut" expression="execution(* xyz.coolblog.aop.*.hello*(..))" /> <!-- advoce --> <aop:before method="before" pointcut-ref="helloPointcut"/> <aop:after method="after" pointcut-ref="helloPointcut"/> </aop:aspect> </aop:config>
如上,一個切面中配置了一個切點和兩個通知,兩個通知均引用了同一個切點,即 pointcut-ref="helloPointcut"。這裏在一個切面中,一個切點對應多個通知,是一對多的關係(能夠配置多個 pointcut,造成多對多的關係)。而在 PointcutAdvisor 的實現類中,切點和通知是一一對應的關係。上面的通知最終會被轉換成兩個 PointcutAdvisor,這裏我把源碼調試的結果貼在下面:
在本節的最後,咱們再來看看 PointcutAdvisor 的實現類 AspectJPointcutAdvisor 的繼承體系圖。以下:
如今咱們有了鏈接點、切點、通知,以及切面等,可謂萬事俱備,可是還差了一股東風。這股東風是什麼呢?沒錯,就是織入。所謂織入就是在切點的引導下,將通知邏輯插入到方法調用上,使得咱們的通知邏輯在方法調用時得以執行。說完織入的概念,如今來講說 Spring 是經過何種方式將通知織入到目標方法上的。先來講說以何種方式進行織入,這個方式就是經過實現後置處理器 BeanPostProcessor 接口。該接口是 Spring 提供的一個拓展接口,經過實現該接口,用戶可在 bean 初始化先後作一些自定義操做。那 Spring 是在什麼時候進行織入操做的呢?答案是在 bean 初始化完成後,即 bean 執行完初始化方法(init-method)。Spring經過切點對 bean 類中的方法進行匹配。若匹配成功,則會爲該 bean 生成代理對象,並將代理對象返回給容器。容器向後置處理器輸入 bean 對象,獲得 bean 對象的代理,這樣就完成了織入過程。關於後置處理器的細節,這裏就很少說了.你們如有興趣,能夠參考我以前寫的Spring IOC 容器源碼分析系列文章。
本篇文章做爲 AOP 源碼分析系列文章的導讀,簡單介紹了 AOP 中的一些術語,及其對應的源碼。總的來講,沒有什麼特別之處。畢竟對於 AOP,你們都有所瞭解。所以,若文中有不妥錯誤之處,還請你們指明。固然,也但願多多指教。
好了,本篇文章先到這裏。感謝你們的閱讀。
本文在知識共享許可協議 4.0 下發布,轉載需在明顯位置處註明出處
做者:coolblog.xyz
本文同步發佈在個人我的博客: http://www.coolblog.xyz
更新時間 | 標題 |
---|---|
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 容器源碼分析 - 餘下的初始化工做 |
更新時間 | 標題 |
---|---|
2018-06-17 | Spring AOP 源碼分析系列文章導讀 |
2018-06-20 | Spring AOP 源碼分析 - 篩選合適的通知器 |
2018-06-20 | Spring AOP 源碼分析 - 建立代理對象 |
2018-06-22 | Spring AOP 源碼分析 - 攔截器鏈的執行過程 |
更新時間 | 標題 |
---|---|
2018-06-29 | Spring MVC 原理探祕 - 一個請求的旅行過程 |
2018-06-30 | Spring MVC 原理探祕 - 容器的建立過程 |
本做品採用知識共享署名-非商業性使用-禁止演繹 4.0 國際許可協議進行許可。