簡介
AOP(Aspect Orient Programming)
AOP 的存在價值
在傳統 OOP 編程裡以對象為核心
我們可以通過分析
現在假設系統中有
圖
看到如圖
為了解決這個問題
圖
對於如圖
對於如圖
因為軟件系統需求變更是很頻繁的事情
根據需求說明書
第一種做法顯然不好
上面想法聽起來很神奇
使用 AspectJ 的編譯時增強進行 AOP
AspectJ 是一個基於 Java 語言的 AOP 框架
AspectJ 是 Java 語言的一個 AOP 實現
AspectJ 是最早
下載
成功安裝了 AspectJ 之後
bin:該路徑下存放了 aj
docs:該路徑下存放了 AspectJ 的使用說明
lib:該路徑下的
相關授權文件
一些文檔
雖然 AspectJ 是 Eclipse 基金組織的開源項目
實際上
首先編寫一個簡單的 Java 類
清單
public class Hello
{
// 定義一個簡單方法
public void sayHello()
{
System
}
// 主方法
public static void main(String[] args)
{
Hello h = new Hello();
h
}
}
上面 Hello 類模擬了一個業務邏輯組件
假設現在客戶需要在執行 sayHello() 方法之前啟動事務
下面我們定義一個特殊的 Java 類
清單
public aspect TxAspect
{
// 指定執行 Hello
void around():call(void Hello
{
System
proceed();
System
}
}
可能讀者已經發現了
上面粗體字代碼也不是方法
正如前面提到的
ajc
我們可以把 ajc
運行該 Hello 類依然無須任何改變
運行該程序
開始事務 …
Hello AspectJ!
事務結束 …
從上面運行結果來看
如果客戶再次提出新需求
清單
public aspect LogAspect
{
// 定義一個 PointCut
// 該 PointCut 對應於指定 Hello 對象的 sayHello 方法
pointcut logPointcut() :execution(void Hello
// 在 logPointcut 之後執行下面代碼塊
after():logPointcut()
{
System
}
}
上面程序的粗體字代碼定義了一個 Pointcut
再次運行 Hello 類
開始事務 …
Hello AspectJ!
記錄日志 …
事務結束 …
從上面運行結果來看
為什麼在對 Hello 類沒有任何修改的前提下
清單
package lee;
import java
import org
public class Hello
{
public void sayHello()
{
try
{
System
LogAspect
LogAspect
}
private static final void sayHello_aroundBody
TxAspect ajc$aspectInstance
{
System
AroundClosure localAroundClosure = ajc$aroundClosure; sayHello_aroundBody
System
}
}
不難發現這個 Hello
提示
實際上
pointcut xxxPointcut()
:execution(void H*
上面程序中的 xxxPointcut 將可以匹配所有以 H 開頭的類中
pointcut xxxPointcut()
:execution(* H*
關於如何定義 AspectJ 中的 Aspect
使用 Spring AOP
與 AspectJ 相同的是
Spring 允許使用 AspectJ Annotation 用於定義方面(Aspect)
簡單地說
為了啟用 Spring 對 @AspectJ 方面配置的支持
xmlns:xsi="-instance"
xmlns:aop=""
xsi:schemaLocation=";
-beans-3.0.xsd
-aop-3.0.xsd">
當然,如果我們希望完全啟動 Spring 的“零配置”功能,則還需要啟用 Spring 的“零配置”支持,讓 Spring 自動搜索指定路徑下 Bean 類。Tw.wiNGWit.Com
所謂自動增強,指的是 Spring 會判斷一個或多個方面是否需要對指定 Bean 進行增強,並據此自動生成相應的代理,從而使得增強處理在合適的時候被調用。
如果不打算使用 Spring 的 XML Schema 配置方式,則應該在 Spring 配置文件中增加如下片段來啟用 @AspectJ 支持。
上面配置文件中的 AnnotationAwareAspectJAutoProxyCreator 是一個 Bean 後處理器(BeanPostProcessor),該 Bean 後處理器將會為容器中 Bean 生成 AOP 代理,
當啟動了 @AspectJ 支持後,只要我們在 Spring 容器中配置一個帶 @Aspect 注釋的 Bean,Spring 將會自動識別該 Bean,並將該 Bean 作為方面 Bean 處理。
在 Spring 容器中配置方面 Bean(即帶 @Aspect 注釋的 Bean),與配置普通 Bean 沒有任何區別,一樣使用
使用 @Aspect 標注一個 Java 類,該 Java 類將會作為方面 Bean,如下面代碼片段所示:
// 使用 @Aspect 定義一個方面類
@Aspect
public class LogAspect
{
// 定義該類的其他內容
...
}
方面類(用 @Aspect 修飾的類)和其他類一樣可以有方法、屬性定義,還可能包括切入點、增強處理定義。
當我們使用 @Aspect 來修飾一個 Java 類之後,Spring 將不會把該 Bean 當成組件 Bean 處理,因此負責自動增強的後處理 Bean 將會略過該 Bean,不會對該 Bean 進行任何增強處理。
開發時無須擔心使用 @Aspect 定義的方面類被增強處理,當 Spring 容器檢測到某個 Bean 類使用了 @Aspect 標注之後,Spring 容器不會對該 Bean 類進行增強。
下面將會考慮采用 Spring AOP 來改寫前面介紹的例子:
下面例子使用一個簡單的 Chinese 類來模擬業務邏輯組件:
清單 5.Chinese.java
@Component
public class Chinese
{
// 實現 Person 接口的 sayHello() 方法
public String sayHello(String name)
{
System.out.println("-- 正在執行 sayHello 方法 --");
// 返回簡單的字符串
return name + " Hello , Spring AOP";
}
// 定義一個 eat() 方法
public void eat(String food)
{
System.out.println("我正在吃 :"+ food);
}
}
提供了上面 Chinese 類之後,接下來假設同樣需要為上面 Chinese 類的每個方法增加事務控制、日志記錄,此時可以考慮使用 Around、AfterReturning 兩種增強處理。
先看 AfterReturning 增強處理代碼。
清單 6.AfterReturningAdviceTest.java
// 定義一個方面
@Aspect
public class AfterReturningAdviceTest
{
// 匹配 org.crazyit.app.service.impl 包下所有類的、
// 所有方法的執行作為切入點
@AfterReturning(returning="rvt", pointcut="execution(* org.crazyit.app.service.impl.*.*(..))")
public void log(Object rvt)
{
System.out.println("獲取目標方法返回值 :" + rvt);
System.out.println("模擬記錄日志功能 ...");
}
}
上面 Aspect 類使用了 @Aspect 修飾,這樣 Spring 會將它當成一個方面 Bean 進行處理。其中程序中粗體字代碼指定將會在調用 org.crazyit.app.service.impl 包下的所有類的所有方法之後織入 log(Object rvt) 方法。
再看 Around 增強處理代碼:
清單 7.AfterReturningAdviceTest.java
// 定義一個方面
@Aspect
public class AroundAdviceTest
{
// 匹配 org.crazyit.app.service.impl 包下所有類的、
// 所有方法的執行作為切入點
@Around("execution(* org.crazyit.app.service.impl.*.*(..))")
public Object processTx(ProceedingJoinPoint jp)
throws java.lang.Throwable
{
System.out.println("執行目標方法之前,模擬開始事務 ...");
// 執行目標方法,並保存目標方法執行後的返回值
Object rvt = jp.proceed(new String[]{"被改變的參數"});
System.out.println("執行目標方法之後,模擬結束事務 ...");
return rvt + " 新增的內容";
}
}
與前面的 AfterReturning 增強處理類似的,此處同樣使用了 @Aspect 來修飾前面 Bean,其中粗體字代碼指定在調用 org.crazyit.app.service.impl 包下的所有類的所有方法的“前後(Around)” 織入 processTx(ProceedingJoinPoint jp) 方法
需要指出的是,雖然此處只介紹了 Spring AOP 的 AfterReturning、Around 兩種增強處理,但實際上 Spring 還支持 Before、After、AfterThrowing 等增強處理,關於 Spring AOP 編程更多、更細致的編程細節,可以參考《輕量級 Java EE 企業應用實戰》一書。
本示例采用了 Spring 的零配置來開啟 Spring AOP,因此上面 Chinese 類使用了 @Component 修飾,而方面 Bean 則使用了 @Aspect 修飾,方面 Bean 中的 Advice 則分別使用了 @AfterReturning、@Around 修飾。接下來只要為 Spring 提供如下配置文件即可:
清單 8.bean.xml
xmlns:xsi="-instance"
xmlns:context=""
xmlns:aop=""xsi:schemaLocation="
-beans-3.0.xsd
-context-3.0.xsd
-aop-3.0.xsd">
,org.crazyit.app.advice">
expression="org.aspectj.lang.annotation.Aspect"/>
清單 9.BeanTest.java
public class BeanTest
{
public static void main(String[] args)
{
// 創建 Spring 容器
ApplicationContext ctx = new
ClassPathXmlApplicationContext("bean.xml");
Chinese p = ctx.getBean("chinese" ,Chinese.class);
System.out.println(p.sayHello("張三"));
p.eat("西瓜");
}
}
從上面開發過程可以看出,對於 Spring AOP 而言,開發者提供的業務組件、方面 Bean 並沒有任何特別的地方。只是方面 Bean 需要使用 @Aspect 修飾即可。程序不需要使用特別的編譯器、織入器進行處理。
運行上面程序,將可以看到如下執行結果:
執行目標方法之前,模擬開始事務 …
– 正在執行 sayHello 方法 –
執行目標方法之後,模擬結束事務 …
獲取目標方法返回值 : 被改變的參數 Hello , Spring AOP 新增的內容
模擬記錄日志功能 …
被改變的參數 Hello , Spring AOP 新增的內容
執行目標方法之前,模擬開始事務 …
我正在吃 : 被改變的參數
執行目標方法之後,模擬結束事務 …
獲取目標方法返回值 :null 新增的內容
模擬記錄日志功能 …
雖然程序是在調用 Chinese 對象的 sayHello、eat 兩個方法,但從上面運行結果不難看出:實際執行的絕對不是 Chinese 對象的方法,而是 AOP 代理的方法。也就是說,Spring AOP 同樣為 Chinese 類生成了 AOP 代理類。這一點可通過在程序中增加如下代碼看出:System.out.println(p.getClass());
上面代碼可以輸出 p 變量所引用對象的實現類,再次執行程序將可以看到上面代碼產生 class org.crazyit.app.service.impl.Chinese$$EnhancerByCGLIB$$290441d2 的輸出,這才是 p 變量所引用的對象的實現類,這個類也就是 Spring AOP 動態生成的 AOP 代理類。從 AOP 代理類的類名可以看出,AOP 代理類是由 CGLIB 來生成的。
如果將上面程序程序稍作修改:只要讓上面業務邏輯類 Chinese 類實現一個任意接口——這種做法更符合 Spring 所倡導的“面向接口編程”的原則。假設程序為 Chinese 類提供如下 Person 接口,並讓 Chinese 類實現該接口:
清單 10.Person.java
public interface Person
{
String sayHello(String name);
void eat(String food);
}
接下來讓 BeanTest 類面向 Person 接口、而不是 Chinese 類編程。即將 BeanTest 類改為如下形式:
清單 11.BeanTest.java
public class BeanTest
{
public static void main(String[] args)
{
// 創建 Spring 容器
ApplicationContext ctx = new
ClassPathXmlApplicationContext("bean.xml");
Person p = ctx.getBean("chinese" ,Person.class);
System.out.println(p.sayHello("張三"));
p.eat("西瓜");
System.out.println(p.getClass());
}
}
原來的程序是將面向 Chinese 類編程,現在將該程序改為面向 Person 接口編程,再次運行該程序,程序運行結果沒有發生改變。只是 System.out.println(p.getClass()); 將會輸出 class $Proxy7,這說明此時的 AOP 代理並不是由 CGLIB 生成的,而是由 JDK 動態代理生成的。
Spring AOP 框架對 AOP 代理類的處理原則是:如果目標對象的實現類實現了接口,Spring AOP 將會采用 JDK 動態代理來生成 AOP 代理類;如果目標對象的實現類沒有實現接口,Spring AOP 將會采用 CGLIB 來生成 AOP 代理類——不過這個選擇過程對開發者完全透明、開發者也無需關心。
Spring AOP 會動態選擇使用 JDK 動態代理、CGLIB 來生成 AOP 代理,如果目標類實現了接口,Spring AOP 則無需 CGLIB 的支持,直接使用 JDK 提供的 Proxy 和 InvocationHandler 來生成 AOP 代理即可。關於如何 Proxy 和 InvocationHandler 來生成動態代理不在本文介紹范圍之內,如果讀者對 Proxy 和 InvocationHandler 的用法感興趣則可自行參考 Java API 文檔或《瘋狂 Java 講義》。
Spring AOP 原理剖析
通過前面介紹可以知道:AOP 代理其實是由 AOP 框架動態生成的一個對象,該對象可作為目標對象使用。AOP 代理包含了目標對象的全部方法,但 AOP 代理中的方法與目標對象的方法存在差異:AOP 方法在特定切入點添加了增強處理,並回調了目標對象的方法.
AOP 代理所包含的方法與目標對象的方法示意圖如圖 3 所示。
圖 3.AOP 代理的方法與目標對象的方法
Spring AOP 實現原理與 CGLIB 應用
Spring 的 AOP 代理由 Spring 的 IoC 容器負責生成、管理,其依賴關系也由 IoC 容器負責管理。因此,AOP 代理可以直接使用容器中的其他 Bean 實例作為目標,這種關系可由 IoC 容器的依賴注入提供。
縱觀 AOP 編程,其中需要程序員參與的只有 3 個部分:
● 定義普通業務組件。
● 定義切入點,一個切入點可能橫切多個業務組件。
● 定義增強處理,增強處理就是在 AOP 框架為普通業務組件織入的處理動作。
上面 3 個部分的第一個部分是最平常不過的事情,無須額外說明。那麼進行 AOP 編程的關鍵就是定義切入點和定義增強處理。一旦定義了合適的切入點和增強處理,AOP 框架將會自動生成 AOP 代理,而 AOP 代理的方法大致有如下公式:
代理對象的方法 = 增強處理 + 被代理對象的方法
在上面這個業務定義中,不難發現 Spring AOP 的實現原理其實很簡單:AOP 框架負責動態地生成 AOP 代理類,這個代理類的方法則由 Advice 和回調目標對象的方法所組成。
對於前面提到的圖 2 所示的軟件調用結構:當方法 1、方法 2、方法 3 ……都需要去調用某個具有“橫切”性質的方法時,傳統的做法是程序員去手動修改方法 1、方法 2、方法 3 ……、通過代碼來調用這個具有“橫切”性質的方法,但這種做法的可擴展性不好,因為每次都要改代碼。
於是 AOP 框架出現了,AOP 框架則可以“動態的”生成一個新的代理類,而這個代理類所包含的方法 1、方法 2、方法 3 ……也增加了調用這個具有“橫切”性質的方法——但這種調用由 AOP 框架自動生成的代理類來負責,因此具有了極好的擴展性。程序員無需手動修改方法 1、方法 2、方法 3 的代碼,程序員只要定義切入點即可—— AOP 框架所生成的 AOP 代理類中包含了新的方法 1、訪法 2、方法 3,而 AOP 框架會根據切入點來決定是否要在方法 1、方法 2、方法 3 中回調具有“橫切”性質的方法。
簡而言之:AOP 原理的奧妙就在於動態地生成了代理類,這個代理類實現了圖 2 的調用——這種調用無需程序員修改代碼。接下來介紹的 CGLIB 就是一個代理生成庫,下面介紹如何使用 CGLIB 來生成代理類。
使用 CGLIB 生成代理類
CGLIB(Code Generation Library),簡單來說,就是一個代碼生成類庫。它可以在運行時候動態是生成某個類的子類。
此處使用前面定義的 Chinese 類,現在改為直接使用 CGLIB 來生成代理,這個代理類同樣可以實現 Spring AOP 代理所達到的效果。
下面先為 CGLIB 提供一個攔截器實現類:
清單 12.AroundAdvice.java
public class AroundAdvice implements MethodInterceptor
{
public Object intercept(Object target, Method method
, Object[] args, MethodProxy proxy)
throws java.lang.Throwable
{
System.out.println("執行目標方法之前,模擬開始事務 ...");
// 執行目標方法,並保存目標方法執行後的返回值
Object rvt = proxy.invokeSuper(target, new String[]{"被改變的參數"});
System.out.println("執行目標方法之後,模擬結束事務 ...");
return rvt + " 新增的內容";
}
}
上面這個 AroundAdvice.java 的作用就像前面介紹的 Around Advice,它可以在調用目標方法之前、調用目標方法之後織入增強處理。
接下來程序提供一個 ChineseProxyFactory 類,這個 ChineseProxyFactory 類會通過 CGLIB 來為 Chinese 生成代理類:
清單 13.ChineseProxyFactory.java
public class ChineseProxyFactory
{
public static Chinese getAuthInstance()
{
Enhancer en = new Enhancer();
// 設置要代理的目標類
en.setSuperclass(Chinese.class);
// 設置要代理的攔截器
en.setCallback(new AroundAdvice());
// 生成代理類的實例
return (Chinese)en.create();
}
}
上面粗體字代碼就是使用 CGLIB 的 Enhancer 生成代理對象的關鍵代碼,此時的 Enhancer 將以 Chinese 類作為目標類,以 AroundAdvice 對象作為“Advice”,程序將會生成一個 Chinese 的子類,這個子類就是 CGLIB 生成代理類,它可作為 Chinese 對象使用,但它增強了 Chinese 類的方法。
測試 Chinese 代理類的主程序如下:
清單 14.Main.java
public class Main
{
public static void main(String[] args)
{
Chinese chin = ChineseProxyFactory.getAuthInstance();
System.out.println(chin.sayHello("孫悟空"));
chin.eat("西瓜");
System.out.println(chin.getClass());
}
}
運行上面主程序,看到如下輸出結果:
執行目標方法之前,模擬開始事務 …
– 正在執行 sayHello 方法 –
執行目標方法之後,模擬結束事務 …
被改變的參數 Hello , CGLIB 新增的內容
執行目標方法之前,模擬開始事務 …
我正在吃 : 被改變的參數
執行目標方法之後,模擬結束事務 …
class lee.Chinese$$EnhancerByCGLIB$$4bd097d9
從上面輸出結果來看,CGLIB 生成的代理完全可以作為 Chinese 對象來使用,而且 CGLIB 代理對象的 sayHello()、eat() 兩個方法已經增加了事務控制(只是模擬),這個 CGLIB 代理其實就是 Spring AOP 所生成的 AOP 代理。
通過程序最後的輸出,不難發現這個代理對象的實現類是 lee.Chinese$$EnhancerByCGLIB$$4bd097d9,這就是 CGLIB 所生成的代理類,這個代理類的格式與前面 Spring AOP 所生成的代理類的格式完全相同。
這就是 Spring AOP 的根本所在:Spring AOP 就是通過 CGLIB 來動態地生成代理對象,這個代理對象就是所謂的 AOP 代理,而 AOP 代理的方法則通過在目標對象的切入點動態地織入增強處理,從而完成了對目標方法的增強。
小結
AOP 廣泛應用於處理一些具有橫切性質的系統級服務,AOP 的出現是對 OOP 的良好補充,它使得開發者能用更優雅的方式處理具有橫切性質的服務。不管是那種 AOP 實現,不論是 AspectJ、還是 Spring AOP,它們都需要動態地生成一個 AOP 代理類,區別只是生成 AOP 代理類的時機不同:AspectJ 采用編譯時生成 AOP 代理類,因此具有更好的性能,但需要使用特定的編譯器進行處理;而 Spring AOP 則采用運行時生成 AOP 代理類,因此無需使用特定編譯器進行處理。由於 Spring AOP 需要在每次運行時生成 AOP 代理,因此性能略差一些
From:http://tw.wingwit.com/Article/program/Java/ky/201311/28367.html