6.2 Spring的AOP
AOP(Aspect Orient Programming),也就是面向切面編程,作為面向?qū)ο缶幊痰囊环N補充。問世的時間并不太長,甚至在國內(nèi)的翻譯還不太統(tǒng)一(有些書翻譯成面向方面編程),但它確實極好地補充了面向?qū)ο缶幊痰姆绞健C嫦驅(qū)ο缶幊虒⒊绦蚍纸獬筛鱾€層次的對象,而面向切面編程將程序運行過程分解成各個切面。
可以這樣理解,面向?qū)ο缶幊淌菑撵o態(tài)角度考慮程序結(jié)構(gòu),面向切面編程是從動態(tài)角度考慮程序運行過程。
Spring AOP是Spring框架的一個重要組件,極好地補充了Spring IoC容器的功能。Spring AOP將Spring IoC容器與AOP組件緊密結(jié)合,豐富了IoC容器的功能。當(dāng)然,即使不使用AOP組件,依然可以使用Spring的IoC容器。
6.2.1 AOP的基本概念
AOP從程序運行角度考慮程序的流程,提取業(yè)務(wù)處理過程的切面。AOP面向的是程序運行中各個步驟,希望以更好的方式來組合業(yè)務(wù)處理的各個步驟。
AOP框架并不與特定的代碼耦合,AOP框架能處理程序執(zhí)行中的特定點,而不是某個具體的程序。AOP框架具有如下兩個特征:
?? ● 各步驟之間的良好隔離性。
?? ● 源代碼無關(guān)性。
下面是關(guān)于面向切面編程的一些術(shù)語:
?? ● 切面,業(yè)務(wù)流程運行的某個特定步驟,就是運行過程的關(guān)注點,關(guān)注點可能橫切多個對象。
?? ● 連接點,程序執(zhí)行過程中明確的點,如方法的調(diào)用或異常的拋出。Spring AOP中,連接點總是方法的調(diào)用,Spring并沒有顯式地使用連接點。
?? ● 處理(Advice),AOP框架在特定的連接點執(zhí)行的動作。處理有around、before和throws等類型。大部分框架都以攔截器作為處理模型。
?? ● 切入點,系列連接點的集合,它確定處理觸發(fā)的時機。AOP框架允許開發(fā)者自己定義切入點,如使用正則表達(dá)式。
?? ● 引入,添加方法或字段到被處理的類。Spring允許引入新的接口到任何被處理的對象。例如,可以使用一個引入,使任何對象實現(xiàn)IsModified接口,以此來簡化緩存。
?? ● 目標(biāo)對象,包含連接點的對象。也稱為被處理對象或被代理對象。
?? ● AOP代理,AOP框架創(chuàng)建的對象,包含處理。簡單地說,代理就是對目標(biāo)對象的加強。Spring中的AOP代理可以是JDK動態(tài)代理,也可以是CGLIB代理。前者為實現(xiàn)接口的目標(biāo)對象的代理,后者為不實現(xiàn)接口的目標(biāo)對象的代理。
注意:面向切面編程是比較前沿的知識,而國內(nèi)大部分翻譯人士翻譯計算機文獻(xiàn)時,總是一邊開著各種詞典和翻譯軟件,一邊逐詞去看文獻(xiàn),不是先從總體上把握知識的架構(gòu)。因此,難免導(dǎo)致一些術(shù)語的翻譯詞不達(dá)意,例如,Socket被翻譯成“套接字”等。在面向切面編程的各術(shù)語翻譯上,也存在較大的差異。對于Advice一詞,有翻譯為“通知”的,有翻譯為“建議”的,如此種種,不一而足。實際上,Advice指AOP框架在特定切面所做的事情,故而筆者翻譯為“處理”,希望可以表達(dá)Advice的真正含義。
6.2.2 AOP的代理
所謂AOP代理,就是AOP框架動態(tài)創(chuàng)建的對象,這個對象通常可以作為目標(biāo)對象的替代品,而AOP代理提供比目標(biāo)對象更加強大的功能。真實的情形是,當(dāng)應(yīng)用調(diào)用AOP代理的方法時,AOP代理會在自己的方法中回調(diào)目標(biāo)對象的方法,從而完成應(yīng)用的調(diào)用。
關(guān)于AOP代理的典型例子就是Spring中的事務(wù)代理Bean。通常,目標(biāo)Bean的方法不是事務(wù)性的,而AOP代理包含目標(biāo)Bean的全部方法,而且這些方法經(jīng)過加強變成了事務(wù)性方法。簡單地說,目標(biāo)對象是藍(lán)本,AOP代理是目標(biāo)對象的加強,在目標(biāo)對象的基礎(chǔ)上,增加屬性和方法,提供更強大的功能。
目標(biāo)對象包含一系列切入點。切入點可以觸發(fā)處理連接點集合。用戶可以自己定義切入點,如使用正則表達(dá)式。AOP代理包裝目標(biāo)對象,在切入點處加入處理。在切入點加入的處理,使得目標(biāo)對象的方法功能更強。
Spring默認(rèn)使用JDK動態(tài)代理實現(xiàn)AOP代理,主要用于代理接口。也可以使用CGLIB代理。實現(xiàn)類的代理,而不是接口。如果業(yè)務(wù)對象沒有實現(xiàn)接口,默認(rèn)使用CGLIB代理。但面向接口編程是良好的習(xí)慣,盡量不要面向具體類編程。因此,業(yè)務(wù)對象通常應(yīng)實現(xiàn)一個或多個接口。
下面是一個簡單動態(tài)代理模式的示例,首先有一個Dog的接口,接口如下:
public interface Dog
{
??? //info方法聲明
??? public void info();
??? //run方法聲明
??? public void run();
}
然后,給出該接口的實現(xiàn)類,實現(xiàn)類必須實現(xiàn)兩個方法,源代碼如下:
public class DogImpl implements Dog
{
??? //info方法實現(xiàn),僅僅打印一個字符串
??? public void info()
??? {
??????? System.out.println("我是一只獵狗");
??? }
??? //run方法實現(xiàn),僅僅打印一個字符串
??? public void run()
??? {
??????? System.out.println("我奔跑迅速");
??? }
}
上面的代碼沒有絲毫獨特之處,是典型的面向接口編程的模型,為了有更好的解耦,采用工廠來創(chuàng)建Dog實例。工廠源代碼如下:
public class DogFactory
{
??? //工廠本身是單態(tài)模式,因此,將DogFactory作為靜態(tài)成員變量保存
??? private static DogFactory df;
??? //將Dog實例緩存
??? private Dog gundog;
??? //默認(rèn)的構(gòu)造器,單態(tài)模式需要的構(gòu)造器是private
??? private DogFactory()
??? {
??? }
??? //單態(tài)模式所需的靜態(tài)方法,該方法是創(chuàng)建本類實例的唯一方法點
??? public static DogFactory instance()
??? {
??????? if (df == null)
??????? {
??????????? df = new DogFactory();
??????? }
??????? return df;
??? }
??? //獲得Dog實例
??? public Dog getDog(String dogName)
??? {
??????? //根據(jù)字符串參數(shù)決定返回的實例
??????? if (dogName.equals("gundog"))
??????? {
??????????? //返回Dog實例之前,先判斷緩存的Dog是否存在,如果不存在才創(chuàng)建,
??????????? 否則直接返回緩存的Dog實例
??????????? if (gundog == null )
??????????? {
??????????????? gundog = new DogImpl();
??????????? }
??????????? return gundog;
??? ???? }
??????? return null;
??? }
}
下面是一個通用的處理類,該處理類沒有與任何特定的類耦合,它可以處理所有的目標(biāo)對象。從JDK 1.3起,Java的import java.lang.reflect下增加InvocationHandler接口,該接口是所有處理類的根接口。
該類處理類的源代碼如下:
public class ProxyHandler implements InvocationHandler
{
??? //需被代理的目標(biāo)對象
??? private Object target;
??? //執(zhí)行代理的目標(biāo)方法時,該invoke方法會被自動調(diào)用
??? public Object invoke(Object proxy, Method method, Object[] args)throws
??? Exception
??? {
??????? Object result = null;
??????? if (method.getName().equals("info"))
??????? {
??????????? System.out.println("======開始事務(wù)...");
??????????? result =method.invoke(target, args);
??????????? System.out.println("======提交事務(wù)...");
??????? }
??????? else
??????? {
??????????? result =method.invoke(target, args);
??????? }
??????? return result;
??? }
??? //通過該方法,設(shè)置目標(biāo)對象
??? public void setTarget(Object o)
??? {
??????? this.target = o;
??? }
}
該處理類實現(xiàn)InvocationHandler接口,實現(xiàn)該接口必須實現(xiàn)invoke(Object proxy, Method method, Object[] args)方法,程序調(diào)用代理的目標(biāo)方法時,自動變成調(diào)用invoke方法。
該處理類并未與任何接口或類耦合,它完全是通用的,它的目標(biāo)實例是Object類型,可以是任何的類型。
在invoke方法內(nèi),對目標(biāo)對象的info方法進(jìn)行簡單加強,在開始執(zhí)行目標(biāo)對象的方法之前,先打印開始事務(wù),執(zhí)行目標(biāo)對象的方法之后,打印提交事務(wù)。
通過method對象的invoke方法,可以完成目標(biāo)對象的方法調(diào)用,執(zhí)行代碼如下:
result =method.invoke(target, args);
下面是代理工廠:
public class MyProxyFactory
{
??? /**
????? * 實例Service對象
???? * @param serviceName String
???? * @return Object
???? */
??? public static Object getProxy(Object object)
??? {
?????? //代理的處理類
??????? ProxyHandler handler = new ProxyHandler();
?????? //把該dog實例托付給代理操作
??????? handler.setTarget(object);
??????? //第一個參數(shù)是用來創(chuàng)建動態(tài)代理的ClassLoader對象,只要該對象能訪問Dog接口
??????? 即可
??????? //第二個參數(shù)是接口數(shù)組,正是代理該接口數(shù)組
??????? //第三個參數(shù)是代理包含的處理實例
??????? return Proxy.newProxyInstance(DogImpl.class.getClassLoader(),
??????????? object.getClass().getInterfaces(),handler);
??? }
}
代理工廠里有一行代碼:
Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),handler);
Proxy.newProxyInstance()方法根據(jù)接口數(shù)組動態(tài)創(chuàng)建代理類實例,接口數(shù)組通過object.getClass().getInterfaces()方法獲得,創(chuàng)建的代理類是JVM在內(nèi)存中動態(tài)創(chuàng)建的,該類實現(xiàn)傳入接口數(shù)組的全部接口。
因此,Dynamic Proxy要求被代理的必須是接口的實現(xiàn)類,否則無法為其構(gòu)造相應(yīng)的動態(tài)類。因此,Spring對接口實現(xiàn)類采用Dynamic Proxy實現(xiàn)AOP,而對沒有實現(xiàn)任何接口的類,則通過CGLIB實現(xiàn)AOP代理。
下面是主程序:
public class TestDog
{
??? public static void main(String[] args)
??? {
??????? Dog dog = null;
??????? //創(chuàng)建Dog實例,該實例將作為被代理對象
??????? Dog targetObject = DogFactory.instance().getDog("gundog");
??????? //以目標(biāo)對象創(chuàng)建代理
??????? Object proxy = MyProxyFactory.getProxy(targetObject);
??????? if (proxy instanceof Dog)
??????? {
??????????? dog = (Dog)proxy;
??????? }
??????? //測試代理的方法
??????? dog.info();
??????? dog.run();
??? }
}
代理實例會實現(xiàn)目標(biāo)對象實現(xiàn)的全部接口。因此,代理實例也實現(xiàn)了Dog接口,程序運行結(jié)果如下:
[java] ======開始事務(wù)...
[java] 我是一只獵狗
[java] ======提交事務(wù)...
[java] 我奔跑迅速
代理實例加強了目標(biāo)對象的方法——僅僅打印了兩行字符串。當(dāng)然,此種加強沒有實際意義。試想一下,若程序中打印字符串的地方,換成真實的事務(wù)開始和事務(wù)提交,則代理實例的方法為目標(biāo)對象的方法增加了事務(wù)性。
6.2.3 創(chuàng)建AOP代理
通過前面的介紹,AOP代理就是由AOP框架動態(tài)生成的一個對象,該對象可作為目標(biāo)對象使用,AOP代理包含了目標(biāo)對象的全部方法。但AOP代理中的方法與目標(biāo)對象的方法存在差異:AOP方法在特定切面插入處理,在處理之間回調(diào)目標(biāo)對象的方法。
AOP代理所包含的方法與目標(biāo)對象所包含的方法的示意圖,如圖6.1所示。
Spring中AOP代理由Spring的IoC容器負(fù)責(zé)生成和管理,其依賴關(guān)系也由IoC容器負(fù)責(zé)管理。因此,AOP代理能夠引用容器中的其他Bean實例,這種引用由IoC容器的依賴注入提供。
Spring的AOP代理大都由ProxyFactoryBean工廠類產(chǎn)生,如圖6.1所示,產(chǎn)生一個AOP代理至少有兩個部分:目標(biāo)對象和AOP框架所加入的處理。因此,配置ProxyFactoryBean時需要確定如下兩個屬性:
?? ● 代理的目標(biāo)對象。
?? ● 處理(Advice)。
注意:關(guān)于Advice的更多知識,此處由于篇幅原因,無法深入討論。實際上,Spring也提供了很多種Advice的實現(xiàn),如需要更深入了解Spring AOP,請讀者參考筆者所著的《Spring2.0寶典》。
所有代理工廠類的父類是org.springframework.aop.framework.ProxyConfig。因此,該類的屬性是所有代理工廠的共同屬性,這些屬性也是非常關(guān)鍵的,包括:
?? ● proxyTargetClass,確定是否代理目標(biāo)類,如果需要代理目標(biāo)是類,該屬性設(shè)為true,此時需要使用CGLIB生成代理;如果代理目標(biāo)是接口,該屬性設(shè)為false,默認(rèn)是false。
?? ● optimize,確定是否使用強優(yōu)化來創(chuàng)建代理。該屬性僅對CGLIB代理有效;對JDK 動態(tài)代理無效。
?? ● frozen,確定是否禁止改變處理,默認(rèn)是false。
?? ● exposeProxy,代理是否可以通過ThreadLocal訪問,如果exposeProxy屬性為true,則可通過AopContext.currentProxy()方法獲得代理。
?? ● aopProxyFactory,所使用的AopProxyFactory具體實現(xiàn)。該參數(shù)用來指定使用動態(tài)代理、CGLIB或其他代理策略。默認(rèn)選擇動態(tài)代理或CGLIB。一般不需要指定該屬性,除非需要使用新的代理類型,才指定該屬性。
配置ProxyFactoryBean工廠bean時,還需要指定它的特定屬性,ProxyFactoryBean的特定屬性如下所示:
?? ● proxyInterfaces,接口名的字符串?dāng)?shù)組。如果沒有確定該參數(shù),默認(rèn)使用CGLIB代理。
?? ● interceptorNames,處理名的字符串?dāng)?shù)組。此處的次序很重要,排在前面的處理,優(yōu)先被調(diào)用。此處的處理名,只能是當(dāng)前工廠中處理的名稱,而不能使用bean引用。處理名字支持使用通配符(*)。
?? ● singleton,工廠是否返回單態(tài)代理。默認(rèn)是true,無論 getObject()被調(diào)用多少次,將返回相同的代理實例。如果需要使用有狀態(tài)的處理——例如,有狀態(tài)的mixin,可改變默認(rèn)設(shè)置,prototype處理。