詳解SSJ(Spring3.x mvc + Spring3.x Core + JPA2.x)輕量級集成開發—第5章 剖析Spring3.x AOP特性01

目錄

一、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)我們又稱之爲靜態代理,該模式的特徵定義是爲委託對象提供一種代理訪問機制,以控制其它對象對其進行訪問。使用代理模式最大的好處在於,實際的角色只需關注具體的業務既可,其餘操作則全權委託給代理對象負責,並且代理操作既可以在業務執行之前執行,同樣也可以在業務執行完成之後再執行。通過這樣的設計,我們的代碼將更具擴展性,代碼結構也更加清晰。

代理模式類圖示例:

 

 

通過上述代理模式示例圖,我們可以發現代理對象與委託對象都需要實現同一目標接口,客戶端訪問的時候,首先會訪問代理類,最後由代理類去訪問委託對象

在這裏筆者設計的代理對象,僅僅只是負責相關業務執行前後的粗粒度的日誌記錄。

代理類型代碼如下:

Java代碼  收藏代碼
  1. public class LoginServiceProxy implements LoginService {  
  2.     private LoginServiceImpl loginServiceImpl;  
  3.     private boolean login;  
  4.     public boolean login() throws Exception {  
  5.         // TODO Auto-generated method stub  
  6.         System.out.println("日誌記錄1...");  
  7.         loginServiceImpl = new LoginServiceImpl();  
  8.         login = loginServiceImpl.login();  
  9.         System.out.println("日誌記錄2...");  
  10.         return login;  
  11.     }  
  12. }  

 

代理模式並不複雜,相對而言筆者覺得它應該是23種設計模式之中最好理解的設計模式之一。如果你對該模式還是無法理解,筆者建議你參考其他相關書籍。

 

四、JDK動態代理實現

JDK動態代理與靜態代理(代理模式)不同的是, 動態代理會在程序運行時,通過反射機制動態生成代理對象。並且使用動態代理後,咱們再也不必手動編寫代理類。這樣不僅能夠簡化開發,相對於靜態代理而言,程序將具備更好的擴展性。

在java.lang.reflect包下,Proxy類型和InvocationHandler接口將用於生成動態代理類。其中Proxy用於創建代理類型或者代理實例,來看看Proxy的常用方法。

用於生成代理類型的靜態getProxyClass方法:

Java代碼  收藏代碼
  1. public static Class<?> getProxyClass(ClassLoader loader,  
  2.             Class<?>... interfaces) throws IllegalArgumentException;  

 

使用Proxy的靜態getProxyClass()方法,我們可以得到一個代理類型。其中“loader”屬性用於指定委託類型的類裝載器,“interfaces”屬性則用於指定委託類型需要實現的所有接口。

 用於生成代理實例的靜態newProxyInstance()方法:

Java代碼  收藏代碼
  1. public static Object newProxyInstance(ClassLoader loader,  
  2.             Class<?>[] interfaces, InvocationHandler h)  
  3.             throws IllegalArgumentException;  

  

使用Proxy的靜態newProxyInstance()方法,我們可以得到一個代理實例。其中“loader”屬性用於指定委託類型的類裝載器,“interfaces”屬性用於指定委託類型需要實現的所有接口,而屬性“h”則用於指定InvocationHandler實現(如果想使用JDK動態代理,那麼代理類務必需實現InvocationHandler接口)。

使用JDK動態代理生成代理對象:

Java代碼  收藏代碼
  1. public class LoginServiceProxy<T> implements InvocationHandler {  
  2.     /* 需要被代理的委託對象 */  
  3.     private T obj;  
  4.   
  5.     public LoginServiceProxy(T obj) {  
  6.         this.obj = obj;  
  7.     }  
  8.   
  9.     public Object invoke(Object proxy, Method method, Object[] args)  
  10.             throws Throwable {  
  11.         // TODO Auto-generated method stub  
  12.         return method.invoke(obj, args);  
  13.     }  
  14.   
  15.     public boolean loginProxy() {  
  16.         boolean login = false;  
  17.         try {  
  18.             /* 反射生成代理對象 */  
  19.             LoginService proxy = (LoginService) Proxy.newProxyInstance(obj  
  20.                     .getClass().getClassLoader(), obj.getClass()  
  21.                     .getInterfaces(), this);  
  22.             System.out.println("日誌記錄1....");  
  23.             login = proxy.login();  
  24.             System.out.println("日誌記錄2....");  
  25.         } catch (Exception e) {  
  26.             // TODO Auto-generated catch block  
  27.             e.printStackTrace();  
  28.         }  
  29.         return login;  
  30.     }  
  31. }  

 

提示:

使用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所需的依賴構件:

Java代碼  收藏代碼
  1. <!-- AspectJ構件依賴 -->  
  2.   <dependency>  
  3.    <groupId>org.aspectj</groupId>  
  4.    <artifactId>aspectjrt</artifactId>  
  5.    <version>1.6.11</version>  
  6.   </dependency>  
  7.   <dependency>  
  8.    <groupId>org.aspectj</groupId>  
  9.    <artifactId>aspectjweaver</artifactId>  
  10.    <version>1.6.11</version>  
  11.   </dependency>  
Java代碼  收藏代碼
  1.   <dependency>  
  2.    <groupId>cglib</groupId>  
  3.    <artifactId>cglib</artifactId>  
  4.    <version>2.1</version>  
  5.   </dependency>  
  6.   <!-- spring構件依賴 -->  
  7.   <dependency>  
  8.    <groupId>org.springframework</groupId>  
  9.    <artifactId>spring-context</artifactId>  
  10.    <version>3.1.0.RELEASE</version>  
  11.   </dependency>  
  12.   <dependency>  
  13.    <groupId>org.springframework</groupId>  
  14.    <artifactId>spring-aop</artifactId>  
  15.    <version>3.1.0.RELEASE</version>  
  16.   </dependency>  
  17.  </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命名空間:

Java代碼  收藏代碼
  1. xmlns:aop="http://www.springframework.org/schema/aop"  
  2. http://www.springframework.org/schema/aop   
  3. http://www.springframework.org/schema/aop/spring-aop-3.1.xsd  

 

成功導入AOP命名空間後,接下來我們還需要在配置文件中進行切面配置。配置切面我們使用的是<aop:aspect/>標籤,該標籤的作用就是將一個普通的POJO轉換成切面類進行相應的代理服務

使用<aop:aspect/>標籤配置切面:

Java代碼  收藏代碼
  1. <aop:config>  
  2.     <aop:aspect id="logAspect" ref="logBean"/>  
  3. </aop:config>  
  4. <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”定義切面執行優先級:

Java代碼  收藏代碼
  1. <aop:config>  
  2.     <aop:aspect id="logAspect2" ref="logBean2" order="2"/>  
  3.     <aop:aspect id="logAspect1" ref="logBean1" order="1"/>  
  4. </aop:config>  
  5. <bean name="logBean1" class="org.johngao.bean.LogBean1" />  
  6. <bean name="logBean2" class="org.johngao.bean.LogBean2" />  

 

上述配置文件中,筆者定義了2個切面。其中切面1的執行優先級爲“1”,所以切面1必然是優先於切面2執行。

 

提示

使用屬性“order”定義切面類的執行優先級,最常用的使用場景是多個切面指定攔截同一切入點

本章內容到此結束,由於時間倉庫,本文或許有很多不盡人意的地方,希望各位能夠理解和體諒。關於下一章的內容,筆者打算繼續講解Spring AOP相關的內容。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章