目錄
一、AOP簡介;
二、爲什麼需要使用AOP;
三、設計模式之代理模式;
四、JDK動態代理實現;
五、Spring AOP簡介;
六、Spring AOP術語;
七、基於Schema配置文件形式的Spring AOP;
前言
經過前面幾個章節的學習,筆者已經詳細的爲大家講解了關於Spring的內核技術(IOC)。那麼從本章開始,筆者將會爲大家講解關於Spring AOP的一些技術。由於AOP的內容涉及範圍較廣,所以筆者仍然打算分成多章進行逐步講解,希望大家能夠耐心進行閱讀。
筆者收到較多私信,很多朋友都是詢問關於後期章節的內容安排,或者直接帖一大堆調試代碼讓筆者解釋原因。對此筆者只能表示無奈,由於時間有限(高質量的博文需要筆者花費大量的時間去構思),加之年前項目週期較緊,所以筆者將不能及時回覆,希望大家給予諒解。在此衷心的祝願大家,新年快樂,獎金豐收。
一、AOP簡介與實現
AOP(Aspect Oriented Programming,面向切面編程)簡單來說就是在不改變源代碼的前提下,通過前後橫切的方式,動態添加新功能,這就是AOP。筆者經常聽到很多朋友喜歡拿AOP與OOP(Object Oriented Programming,面向對象編程)進行比較,而且還分析得津津有味(曾出現在某些高校的學術論文上)。在此筆者希望大家能夠明白AOP僅僅只是作爲OOP的一種特性補充,如果脫離OOP,AOP將一無是處。
OOP延伸自POP(Procedure-Oriented Programming,面向過程編程),這纔是一種創新的軟件設計思想。換句話來說AOP其實是基於OOP的,算是一種改進,一種補充,但絕對算不上是一次實質性的飛躍。OOP是針對領域模型中的組件或者組件單元進行封裝或抽象,其結構是按照順序結構從上至下逐一執行。並且組件之間存在着相互關聯,相互依賴,以高耦合的方式展現領域對象的職責,但這同時也暴露了OOP思想的缺陷(OOP本身就是基於依賴式設計)。而OOA的特性是模塊橫切點,與OOP不同的是,OOA更加關注內聚性。也就是說OOA是針對業務邏輯或控制邏輯中存在的通用模塊進行抽取,所抽取出來的單元模塊我們稱之爲橫切點。並且在許多情況下,這些橫切點並不屬於業務邏輯或業務控制成員,AOP要做的事情就是對其進行逐步分離,然後讓這些橫切點以可插拔式的設計與其進行耦合。從這一點來看AOP和OOP的思想是截然不同的,所以從理論上來說你完全可以將OOA當做是對OOP的一種解耦優化。
二、爲什麼需要使用AOP
隨着Object Oriented思想的大行其道。上至架構人員,下至是開發人員,心裏邊每天都在不停的思考着,如何讓我的設計,我的代碼更好的解耦,或許這便是軟件工程永恆的話題。但是筆者同時也要告訴你,世界上沒有最好的解耦方式。只要我們能夠儘可能做到面向接口編程,遵循類型單一原則設計,善用設計模式,這樣我們的設計才更具複用性、維護性、擴展性及伸縮性。
筆者上述章節已經提到過,AOP可以當做是OOP的一種解耦優化,那麼接下來咱們就來看看AOP到底是以何種形式對OOP進行解耦的。首先來看基於OOP的設計,假設筆者有2個控制層(實際情況可能會更多),都需要請求同一業務操作響應處理。但是在請求之前,控制邏輯有必要對其進行一系列的處理操作,這些處理操作可能包含:權限檢查、粗粒度日誌記錄等。如果按照OOP的設計思想咱們應該怎麼做呢?很可能你的第一反應會首先編寫一個通用的控制邏輯接口,然後指定其派生類實現重寫所需的一系列處理操作。最後控制層在調用具體的業務操作之前(或者業務操作自身在執行之前),務必先要調用封裝好的通用控制邏輯。如果你也是這麼設計的,那麼恭喜你,OOA(Object-Oriented Analysis,面向對象分析)和OOD(Object-Oriented Design,面向對象設計)你確實掌握的不錯。
基於OOP的類圖設計:
當你爲上述設計沾沾自喜的時候,請花一分鐘時間思考一下。控制層組件與通用控制邏輯完全是處於高耦合狀態,先不論控制邏輯接口是否還具備有效性,只要控制邏輯稍稍發生些許變化,便會直接影響到所有控制層,這便基於依賴式設計的弊端。有辦法可以解決嗎?千萬別告訴筆者Object Oriented只能這樣,如果你真的是這麼想的,那麼你就可以考慮結合AOP的方式重構上述設計。
如果使用AOP的設計方式咱們應該怎麼做呢?筆者前面提到過,AOP的特性是模塊橫切點,在這裏這個橫切點其實就是通用的控制邏輯,現在要做的事情就是需要將其抽取出來,使之與我們的控制層組件分離,這樣既提升了內聚性,同時也保證了設計的低耦合。
基於OOA思想重構上述設計圖:
通過基於上述AOP的設計重構,咱們完全實現了相關處理與控制層組件之間的分離。在這裏筆者要給大家提示一下,如果想在程序中完全解除組件耦合是做不到的,且是不實際的。因爲Object Oriented本身就是基於依賴式設計,如果希望設計出來的東西沒有任何耦合性,恐怕你只能退回到POP才能滿足你的需求。
對於目前而言,基於AOP的技術實現可以應用在諸多領域,比如:粗粒度日誌記錄、性能統計、安全控制、事務處理或者異常處理上。目前優秀的AOP產品其實也挺多的,除了Spring以外,Struts的Interceptor(攔截器)其實也是基於AOP思想的實現。
三、設計模式之代理模式
其實AOP的概念並不是近幾年才誕生的新東西,早在GOF提出23種設計模式的時候,我們已經可以看見AOP的雛形身影。不知大家是否記得熟悉的代理模式?你完全可以把它當做是AOP的特定實現,當然如果你本身並不瞭解代理模式,請仔細的閱讀筆者接下來的講義。
回顧第1章,筆者爲大家講解了23種設計模式之中的工廠模式系列(包括:簡單工廠模式、工廠方法模式和抽象工廠模式),本章節筆者則會爲大家講解關於代理模式的使用方式。
代理模式(Proxy Pattern)我們又稱之爲靜態代理,該模式的特徵定義是爲委託對象提供一種代理訪問機制,以控制其它對象對其進行訪問。使用代理模式最大的好處在於,實際的角色只需關注具體的業務既可,其餘操作則全權委託給代理對象負責,並且代理操作既可以在業務執行之前執行,同樣也可以在業務執行完成之後再執行。通過這樣的設計,我們的代碼將更具擴展性,代碼結構也更加清晰。
代理模式類圖示例:
通過上述代理模式示例圖,我們可以發現代理對象與委託對象都需要實現同一目標接口,客戶端訪問的時候,首先會訪問代理類,最後由代理類去訪問委託對象。
在這裏筆者設計的代理對象,僅僅只是負責相關業務執行前後的粗粒度的日誌記錄。
代理類型代碼如下:
- public class LoginServiceProxy implements LoginService {
- private LoginServiceImpl loginServiceImpl;
- private boolean login;
- public boolean login() throws Exception {
- // TODO Auto-generated method stub
- System.out.println("日誌記錄1...");
- loginServiceImpl = new LoginServiceImpl();
- login = loginServiceImpl.login();
- System.out.println("日誌記錄2...");
- return login;
- }
- }
代理模式並不複雜,相對而言筆者覺得它應該是23種設計模式之中最好理解的設計模式之一。如果你對該模式還是無法理解,筆者建議你參考其他相關書籍。
四、JDK動態代理實現
JDK動態代理與靜態代理(代理模式)不同的是, 動態代理會在程序運行時,通過反射機制動態生成代理對象。並且使用動態代理後,咱們再也不必手動編寫代理類。這樣不僅能夠簡化開發,相對於靜態代理而言,程序將具備更好的擴展性。
在java.lang.reflect包下,Proxy類型和InvocationHandler接口將用於生成動態代理類。其中Proxy用於創建代理類型或者代理實例,來看看Proxy的常用方法。
用於生成代理類型的靜態getProxyClass方法:
- public static Class<?> getProxyClass(ClassLoader loader,
- Class<?>... interfaces) throws IllegalArgumentException;
使用Proxy的靜態getProxyClass()方法,我們可以得到一個代理類型。其中“loader”屬性用於指定委託類型的類裝載器,“interfaces”屬性則用於指定委託類型需要實現的所有接口。
用於生成代理實例的靜態newProxyInstance()方法:
- public static Object newProxyInstance(ClassLoader loader,
- Class<?>[] interfaces, InvocationHandler h)
- throws IllegalArgumentException;
使用Proxy的靜態newProxyInstance()方法,我們可以得到一個代理實例。其中“loader”屬性用於指定委託類型的類裝載器,“interfaces”屬性用於指定委託類型需要實現的所有接口,而屬性“h”則用於指定InvocationHandler實現(如果想使用JDK動態代理,那麼代理類務必需實現InvocationHandler接口)。
使用JDK動態代理生成代理對象:
- public class LoginServiceProxy<T> implements InvocationHandler {
- /* 需要被代理的委託對象 */
- private T obj;
- public LoginServiceProxy(T obj) {
- this.obj = obj;
- }
- public Object invoke(Object proxy, Method method, Object[] args)
- throws Throwable {
- // TODO Auto-generated method stub
- return method.invoke(obj, args);
- }
- public boolean loginProxy() {
- boolean login = false;
- try {
- /* 反射生成代理對象 */
- LoginService proxy = (LoginService) Proxy.newProxyInstance(obj
- .getClass().getClassLoader(), obj.getClass()
- .getInterfaces(), this);
- System.out.println("日誌記錄1....");
- login = proxy.login();
- System.out.println("日誌記錄2....");
- } catch (Exception e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- return login;
- }
- }
提示:
使用JDK動態代理後,代理類型的名稱則會以“$Proxy”開後。我們通過使用Debug來看一下代理類型的名稱:
五、Spring AOP簡介
關於Spring AOP中牽扯的概念較多,但筆者並不打算像其他技術文章一樣故弄玄虛的瞎扯,儘可能的爲大家帶來最直接的表述方式。上述章節筆者詳細的爲大家闡述了AOP的一些相關概念和特定實現方式,從本章開始筆者將會爲大家講解如何在Spring中使用AOP。當然在正式開始講解之前,你完全沒有必要把Spring AOP想的過於複雜化,因爲在Spring中使用AOP是一件極其輕鬆的事情,我們除了需要把對象管理(依賴建立、依賴維護、依賴銷燬)交由Spring的IOC容器去負責外,代理方式則全部委派給Spring的AOP去完成即可。
從大致上來說使用Spring AOP其實有3種方式,第一種是採用實現MethodBeforeAdvice、AfterReturningAdvice、MethodInterceptor等接口的方式(在Spring1.x版本中使用最多)。第二種是基於Schema風格的配置文件方式。最後一種便是基於Annotation的實現方式,至於具體使用哪一種就看你自己喜好或項目需要。本章筆者僅圍繞配置文件和Annotation的方式進行講解,如果大家想了解基於指定接口的方式實現Spring AOP,筆者推薦你參考Spring官方幫助文檔。
Spring AOP底層實現方式採用了2種代理機制,分別爲:JDK動態代理和CGLib動態代理。至於爲什麼需要使用這2種代理機制,很大程度上是因爲JDK自身只支持基於接口的代理,而不支持類型的代理。對於開發人員而言,要做的事情僅僅只是使用Spring封裝好的AOP實現即可,不必太過於關注底層實現細節。
六、Spring AOP術語
在學習Spring AOP的時候,筆者不得不爲大家提及一些AOP相關的術語概念,因爲理解這些概念或許能夠使你更快的掌握Spring AOP。筆者在很多年前學習Spring AOP術語時,曾經被國內一些“高手”的所謂翻譯弄得暈頭轉向。在此筆者不得不“佩服”國內某些IT人員,把通俗易懂的英文翻譯得連鬼都看不懂,筆者實在不解,所以筆者將會以自己的理解對以下AOP術語進行概念性總結:
1、切面(Aspect ):用於執行代理業務的具體內容;
2、連接點(Join point ):用於執行切面的具體代碼位置;
3、通知(Advice ):用於執行代理業務的具體內容實現;
4、切入點(Pointcut):定義了通知應該攔截的方法;
5、目標對象(Target object):委託對象;
6、AOP代理(Aop proxy):代理對象;
7、前置通知(Before advice):代理業務執行於被攔截的方法之前;
8、後置通知(After advice):代理業務執行於被攔截的方法之後;
9、環繞通知(Around advice):代理業務執行於被攔截的方法之前或之後;
10、異常通知(After throwing advice):代理業務執行於被攔截的方法拋出異常後;
11、返回時通知(After returning advice):代理業務執行於被攔截的方法返回之前;
七、基於Schema配置文件形式的Spring AOP
筆者在前面章節中曾經提到過,如果想使用Spring AOP其實是有3種方式的。但筆者僅僅只會圍繞Annotation和基於Schema風格的配置文件方式進行講解。咱們先來看看如何使用配置文件的方式實現Spring AOP,或許這並不複雜,你完全可以輕鬆掌握並使用。
由於筆者的項目是基於Maven3.x進行管理的,所以首先需要添加Spring AOP所需的依賴構件:
- <!-- AspectJ構件依賴 -->
- <dependency>
- <groupId>org.aspectj</groupId>
- <artifactId>aspectjrt</artifactId>
- <version>1.6.11</version>
- </dependency>
- <dependency>
- <groupId>org.aspectj</groupId>
- <artifactId>aspectjweaver</artifactId>
- <version>1.6.11</version>
- </dependency>
- <dependency>
- <groupId>cglib</groupId>
- <artifactId>cglib</artifactId>
- <version>2.1</version>
- </dependency>
- <!-- spring構件依賴 -->
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-context</artifactId>
- <version>3.1.0.RELEASE</version>
- </dependency>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-aop</artifactId>
- <version>3.1.0.RELEASE</version>
- </dependency>
- </dependencies>
當然如果你只是使用傳統工程構建Spring應用的話,你僅需要下載Spring AOP的相關構件引入至項目中即可,這些構件包括:
1、aspectjrt-version.jar;
2、spring-aop-version.jar;
3、aopalliance-version.jar;
4、aspectjweaver.jar;
5、cglib-version.jar;
6、asm-all-version.jar;
當我們成功添加Spring AOP的所需構件後,還需導入AOP命名空間:
- xmlns:aop="http://www.springframework.org/schema/aop"
- http://www.springframework.org/schema/aop
- http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
成功導入AOP命名空間後,接下來我們還需要在配置文件中進行切面配置。配置切面我們使用的是<aop:aspect/>標籤,該標籤的作用就是將一個普通的POJO轉換成切面類進行相應的代理服務。
使用<aop:aspect/>標籤配置切面:
- <aop:config>
- <aop:aspect id="logAspect" ref="logBean"/>
- </aop:config>
- <bean name="logBean" class="org.johngao.bean.LogBean" />
在上述配置文件中,<aop:config/>標籤作爲Spring AOP的切面根標籤。該標籤中包含有<aop:advisor/>、<aop:aspect/>、<aop:pointcut/>等3的子標籤元素,其中<aop:config/>標籤用於定義切面服務,<aop:advisor/>標籤類似於自包含切面,<aop:pointcut/>標籤則用於定義切入點。大家在使用這3個標籤的時候需要注意,必須按照pointcut-->advisor-->aspect的順序進行聲明。配置文件中允許定義多個<aop:config/>標籤,且該標籤內部仍然允許定義多個子標籤元素。
我們首先來看<aop:aspect/>標籤的使用。筆者前面也曾提及過,該標籤用於定義切面,其中屬性“ref”用於引用POJO作爲切面類。此外<aop:aspect/>標籤中還包含有2個可選屬性,分別爲“id”和“order”。其中屬性“id”定義了切面的標識名。而屬性“order”定義了切面類的優先級,屬性值越低,切面的執行優先級就越高。
使用屬性“order”定義切面執行優先級:
- <aop:config>
- <aop:aspect id="logAspect2" ref="logBean2" order="2"/>
- <aop:aspect id="logAspect1" ref="logBean1" order="1"/>
- </aop:config>
- <bean name="logBean1" class="org.johngao.bean.LogBean1" />
- <bean name="logBean2" class="org.johngao.bean.LogBean2" />
上述配置文件中,筆者定義了2個切面。其中切面1的執行優先級爲“1”,所以切面1必然是優先於切面2執行。
提示:
使用屬性“order”定義切面類的執行優先級,最常用的使用場景是多個切面指定攔截同一切入點。
本章內容到此結束,由於時間倉庫,本文或許有很多不盡人意的地方,希望各位能夠理解和體諒。關於下一章的內容,筆者打算繼續講解Spring AOP相關的內容。