Spring AOP使用動(dòng)態(tài)代理技術(shù)在運(yùn)行期織入增強(qiáng)的代碼,為了揭示Spring AOP底層的工作機(jī)理,有必要對(duì)涉及到的Java知識(shí)進(jìn)行學(xué)習(xí)。Spring AOP使用了兩種代理機(jī)制:一種是基于JDK的動(dòng)態(tài)代理;另一種是基于CGLib的動(dòng)態(tài)代理。之所以需要兩種代理機(jī)制,很大程度上是因?yàn)镴DK本身只提供接口的代理,而不支持類(lèi)的代理。
帶有橫切邏輯的實(shí)例 我們通過(guò)具體化代碼實(shí)現(xiàn)上一節(jié)所介紹例子的性能監(jiān)視橫切邏輯,并通過(guò)動(dòng)態(tài)代理技術(shù)對(duì)此進(jìn)行改造。在調(diào)用每一個(gè)目標(biāo)類(lèi)方法時(shí)啟動(dòng)方法的性能監(jiān)視,在目標(biāo)類(lèi)方法調(diào)用完成時(shí)記錄方法的花費(fèi)時(shí)間。
代碼清單6-2 ForumService:包含性能監(jiān)視橫切代碼
- package com.baobaotao.proxy;
- public class ForumServiceImpl implements ForumService {
- public void removeTopic(int topicId) {
-
-
- PerformanceMonitor.begin(
- "com.baobaotao.proxy.ForumServiceImpl. removeTopic");
- System.out.println("模擬刪除Topic記錄:"+topicId);
- try {
- Thread.currentThread().sleep(20);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
-
-
- PerformanceMonitor.end();
- }
-
- public void removeForum(int forumId) {
-
- PerformanceMonitor.begin(
- "com.baobaotao.proxy.ForumServiceImpl. removeForum");
- System.out.println("模擬刪除Forum記錄:"+forumId);
- try {
- Thread.currentThread().sleep(40);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
-
-
- PerformanceMonitor.end();
- }
- }
package com.baobaotao.proxy;
public class ForumServiceImpl implements ForumService {
public void removeTopic(int topicId) {
//①-1開(kāi)始對(duì)該方法進(jìn)行性能監(jiān)視
PerformanceMonitor.begin(
"com.baobaotao.proxy.ForumServiceImpl. removeTopic");
System.out.println("模擬刪除Topic記錄:"+topicId);
try {
Thread.currentThread().sleep(20);
} catch (Exception e) {
throw new RuntimeException(e);
}
//①-2結(jié)束對(duì)該方法進(jìn)行性能監(jiān)視
PerformanceMonitor.end();
}
public void removeForum(int forumId) {
//②-1開(kāi)始對(duì)該方法進(jìn)行性能監(jiān)視
PerformanceMonitor.begin(
"com.baobaotao.proxy.ForumServiceImpl. removeForum");
System.out.println("模擬刪除Forum記錄:"+forumId);
try {
Thread.currentThread().sleep(40);
} catch (Exception e) {
throw new RuntimeException(e);
}
//②-2結(jié)束對(duì)該方法進(jìn)行性能監(jiān)視
PerformanceMonitor.end();
}
}
代碼清單6-2中粗體表示的代碼就是具有橫切邏輯特征的代碼,每個(gè)Service類(lèi)和每個(gè)業(yè)務(wù)方法體的前后都執(zhí)行相同的代碼邏輯:方法調(diào)用前啟動(dòng)PerformanceMonitor,方法調(diào)用后通知PerformanceMonitor結(jié)束性能監(jiān)視并給記錄性能監(jiān)視結(jié)果。
PerformanceMonitor是性能監(jiān)視的實(shí)現(xiàn)類(lèi),我們給出一個(gè)非常簡(jiǎn)單的實(shí)現(xiàn)版本,其代碼如代碼清單6-3所示:
代碼清單6-3 PerformanceMonitor
- package com.baobaotao.proxy;
- public class PerformanceMonitor {
-
- private static ThreadLocal<MethodPerformace> performanceRecord =
- new ThreadLocal<MethodPerformance>();
-
-
- public static void begin(String method) {
- System.out.println("begin monitor...");
- MethodPerformance mp = new MethodPerformance(method);
- performanceRecord.set(mp);
- }
- public static void end() {
- System.out.println("end monitor...");
- MethodPerformance mp = performanceRecord.get();
-
-
- mp.printPerformance();
- }
- }
package com.baobaotao.proxy;
public class PerformanceMonitor {
//①通過(guò)一個(gè)ThreadLocal保存調(diào)用線程相關(guān)的性能監(jiān)視信息
private static ThreadLocal<MethodPerformace> performanceRecord =
new ThreadLocal<MethodPerformance>();
//②啟動(dòng)對(duì)某一目標(biāo)方法的性能監(jiān)視
public static void begin(String method) {
System.out.println("begin monitor...");
MethodPerformance mp = new MethodPerformance(method);
performanceRecord.set(mp);
}
public static void end() {
System.out.println("end monitor...");
MethodPerformance mp = performanceRecord.get();
//③打印出方法性能監(jiān)視的結(jié)果信息。
mp.printPerformance();
}
}
ThreadLocal是將非線程安全類(lèi)改造為線程安全類(lèi)的法寶,在9.2節(jié)中我們將詳細(xì)介紹這個(gè)Java基礎(chǔ)知識(shí)。PerformanceMonitor提供了兩個(gè)方法:通過(guò)調(diào)用begin(String method)方法開(kāi)始對(duì)某個(gè)目標(biāo)類(lèi)方法的監(jiān)視,method為目標(biāo)類(lèi)方法的全限定名;而end()方法結(jié)束對(duì)目標(biāo)類(lèi)方法的監(jiān)視,并給出性能監(jiān)視的信息。這兩個(gè)方法必須配套使用。
用于記錄性能監(jiān)視信息的MethodPerformance類(lèi)的代碼如所示:
代碼清單6-4 MethodPerformance
- package com.baobaotao.proxy;
- public class MethodPerformance {
- private long begin;
- private long end;
- private String serviceMethod;
- public MethodPerformance(String serviceMethod){
- this.serviceMethod = serviceMethod;
-
-
- this.begin = System.currentTimeMillis();
-
- }
- public void printPerformance(){
-
-
- end = System.currentTimeMillis();
- long elapse = end - begin;
-
-
- System.out.println(serviceMethod+"花費(fèi)"+elapse+"毫秒。");
- }
- }
package com.baobaotao.proxy;
public class MethodPerformance {
private long begin;
private long end;
private String serviceMethod;
public MethodPerformance(String serviceMethod){
this.serviceMethod = serviceMethod;
//①記錄目標(biāo)類(lèi)方法開(kāi)始執(zhí)行點(diǎn)的系統(tǒng)時(shí)間
this.begin = System.currentTimeMillis();
}
public void printPerformance(){
//②獲取目標(biāo)類(lèi)方法執(zhí)行完成后的系統(tǒng)時(shí)間,并進(jìn)而計(jì)算出目標(biāo)類(lèi)方法執(zhí)行時(shí)間
end = System.currentTimeMillis();
long elapse = end - begin;
//③報(bào)告目標(biāo)類(lèi)方法的執(zhí)行時(shí)間
System.out.println(serviceMethod+"花費(fèi)"+elapse+"毫秒。");
}
}
通過(guò)下面的代碼測(cè)試擁有性能監(jiān)視能力的ForumServiceImpl業(yè)務(wù)方法:
- package com.baobaotao.proxy;
-
- public class TestForumService {
- public static void main(String[] args) {
- ForumService forumService = new ForumServiceImpl();
- forumService .removeForum(10);
- forumService .removeTopic(1012);
- }
- }
package com.baobaotao.proxy;
public class TestForumService {
public static void main(String[] args) {
ForumService forumService = new ForumServiceImpl();
forumService .removeForum(10);
forumService .removeTopic(1012);
}
}
我們得到以下輸出信息:
引用
begin monitor... ①removeForum(10)方法的性能監(jiān)視報(bào)告
模擬刪除Forum記錄:10
end monitor...
com.baobaotao.proxy.ForumServiceImpl.removeForum花費(fèi)47毫秒。
begin monitor... ①removeTopic(1012)方法的性能監(jiān)視報(bào)告
模擬刪除Topic記錄:1012
end monitor...
com.baobaotao.proxy.ForumServiceImpl.removeTopic花費(fèi)26毫秒。
正如代碼清單6 2實(shí)例所示,當(dāng)某個(gè)方法需要進(jìn)行性能監(jiān)視,就必須調(diào)整方法代碼,在方法體前后分別添加上開(kāi)啟性能監(jiān)視和結(jié)束性能監(jiān)視的代碼。這些非業(yè)務(wù)邏輯的性能監(jiān)視代碼破壞了ForumServiceImpl業(yè)務(wù)邏輯的純粹性。我們希望通過(guò)代理的方式,將業(yè)務(wù)類(lèi)方法中開(kāi)啟和結(jié)束性能監(jiān)視的這些橫切代碼從業(yè)務(wù)類(lèi)中完全移除。并通過(guò)JDK動(dòng)態(tài)代理技術(shù)或CGLib動(dòng)態(tài)代理技術(shù)將橫切代碼動(dòng)態(tài)織入到目標(biāo)方法的相應(yīng)位置。
JDK動(dòng)態(tài)代理 JDK 1.3以后,Java提供了動(dòng)態(tài)代理的技術(shù),允許開(kāi)發(fā)者在運(yùn)行期創(chuàng)建接口的代理實(shí)例。在Sun剛推出動(dòng)態(tài)代理時(shí),還很難想象它有多大的實(shí)際用途,現(xiàn)在我們終于發(fā)現(xiàn)動(dòng)態(tài)代理是實(shí)現(xiàn)AOP的絕好底層技術(shù)。
JDK的動(dòng)態(tài)代理主要涉及到j(luò)ava.lang.reflect包中的兩個(gè)類(lèi):Proxy和InvocationHandler。其中InvocationHandler是一個(gè)接口,可以通過(guò)實(shí)現(xiàn)該接口定義橫切邏輯,并通過(guò)反射機(jī)制調(diào)用目標(biāo)類(lèi)的代碼,動(dòng)態(tài)將橫切邏輯和業(yè)務(wù)邏輯編織在一起。
而Proxy利用InvocationHandler動(dòng)態(tài)創(chuàng)建一個(gè)符合某一接口的實(shí)例,生成目標(biāo)類(lèi)的代理對(duì)象。這樣講一定很抽象,我們馬上著手使用Proxy和InvocationHandler這兩個(gè)魔法戒對(duì)上一節(jié)中的性能監(jiān)視代碼進(jìn)行革新。
首先,我們從業(yè)務(wù)類(lèi)ForumServiceImpl中刪除性能監(jiān)視的橫切代碼,使ForumServiceImpl只負(fù)責(zé)具體的業(yè)務(wù)邏輯,如代碼清單6-5所示:
代碼清單6-5 ForumServiceImpl:移除性能監(jiān)視橫切代碼
- package com.baobaotao.proxy;
-
- public class ForumServiceImpl implements ForumService {
-
- public void removeTopic(int topicId) {
- ①
- System.out.println("模擬刪除Topic記錄:"+topicId);
- try {
- Thread.currentThread().sleep(20);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- ①
- }
- public void removeForum(int forumId) {
- ②
- System.out.println("模擬刪除Forum記錄:"+forumId);
- try {
- Thread.currentThread().sleep(40);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- ②
- }
- }
package com.baobaotao.proxy;
public class ForumServiceImpl implements ForumService {
public void removeTopic(int topicId) {
①
System.out.println("模擬刪除Topic記錄:"+topicId);
try {
Thread.currentThread().sleep(20);
} catch (Exception e) {
throw new RuntimeException(e);
}
①
}
public void removeForum(int forumId) {
②
System.out.println("模擬刪除Forum記錄:"+forumId);
try {
Thread.currentThread().sleep(40);
} catch (Exception e) {
throw new RuntimeException(e);
}
②
}
}
在代碼清單6-5中的①和②處,原來(lái)的性能監(jiān)視代碼被移除了,我們只保留了真正的業(yè)務(wù)邏輯。
從業(yè)務(wù)類(lèi)中移除的性能監(jiān)視橫切代碼當(dāng)然不能漂浮在空氣中,它還得找到一個(gè)安身之所,InvocationHandler就是橫切代碼的安家樂(lè)園,我們將性能監(jiān)視的代碼安置在PerformanceHandler中,如代碼清單6-6所示:
- 代碼清單6-6 PerformanceHandler
- package com.baobaotao.proxy;
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
-
- public class PerformanceHandler implements InvocationHandler {
- private Object target;
- public PerformanceHandler(Object target){
- this.target = target;
- }
- public Object invoke(Object proxy, Method method, Object[] args) ③
- throws Throwable {
- PerformanceMonitor.begin(target.getClass().getName()+"."+ method. getName());③-1
- Object obj = method.invoke(target, args);
- PerformanceMonitor.end();③-1
- return obj;
- }
- }
代碼清單6-6 PerformanceHandler
package com.baobaotao.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class PerformanceHandler implements InvocationHandler {//①實(shí)現(xiàn)InvocationHandler
private Object target;
public PerformanceHandler(Object target){ //②target為目標(biāo)的業(yè)務(wù)類(lèi)
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) ③
throws Throwable {
PerformanceMonitor.begin(target.getClass().getName()+"."+ method. getName());③-1
Object obj = method.invoke(target, args);// ③-2通過(guò)反射方法調(diào)用業(yè)務(wù)類(lèi)的目標(biāo)方法
PerformanceMonitor.end();③-1
return obj;
}
}
③處invoke()方法中粗體所示部分的代碼為性能監(jiān)視的橫切代碼,我們發(fā)現(xiàn),橫切代碼只出現(xiàn)一次,而不是原來(lái)那樣星灑各處。③-2處的method.invoke()語(yǔ)句通過(guò)Java反射機(jī)制間接調(diào)用目標(biāo)對(duì)象的方法,這樣InvocationHandler的invoke()方法就將橫切邏輯代碼(③-1)和業(yè)務(wù)類(lèi)方法的業(yè)務(wù)邏輯代碼(③-2)編織到一起了,所以我們可以將InvocationHandler看成是一個(gè)編織器。下面,我們對(duì)這段代碼做進(jìn)一步的說(shuō)明。
首先,我們實(shí)現(xiàn)InvocationHandler接口,該接口定義了一個(gè) invoke(Object proxy, Method method, Object[] args)的方法,proxy是最終生成的代理實(shí)例,一般不會(huì)用到;method是被代理目標(biāo)實(shí)例的某個(gè)具體方法,通過(guò)它可以發(fā)起目標(biāo)實(shí)例方法的反射調(diào)用;args是通過(guò)被代理實(shí)例某一個(gè)方法的入?yún)ⅲ诜椒ǚ瓷湔{(diào)用時(shí)使用。
此外,我們?cè)跇?gòu)造函數(shù)里通過(guò)target傳入希望被代理的目標(biāo)對(duì)象,如②處所示,在InvocationHandler接口方法invoke(Object proxy, Method method, Object[] args)里,將目標(biāo)實(shí)例傳給method.invoke()方法,調(diào)用目標(biāo)實(shí)例的方法,如③所示。
下面,我們通過(guò)Proxy結(jié)合PerformanceHandler創(chuàng)建ForumService接口的代理實(shí)例,如代碼清單6-7所示:
代碼清單6-7 TestForumService:創(chuàng)建代理實(shí)例
- package com.baobaotao.proxy;
- import java.lang.reflect.Proxy;
- public class TestForumService {
- public static void main(String[] args) {
-
-
- ForumService target = new ForumServiceImpl();
-
-
- PerformanceHandler handler = new PerformanceHandler(target);
-
-
- ForumService proxy = (ForumService) Proxy.newProxyInstance(
- target.getClass().getClassLoader(),
- target.getClass().getInterfaces(),
- handler);
-
-
- proxy.removeForum(10);
- proxy.removeTopic(1012);
- }
- }
package com.baobaotao.proxy;
import java.lang.reflect.Proxy;
public class TestForumService {
public static void main(String[] args) {
//①希望被代理的目標(biāo)業(yè)務(wù)類(lèi)
ForumService target = new ForumServiceImpl();
//②將目標(biāo)業(yè)務(wù)類(lèi)和橫切代碼編織到一起
PerformanceHandler handler = new PerformanceHandler(target);
//③根據(jù)編織了目標(biāo)業(yè)務(wù)類(lèi)邏輯和性能監(jiān)視橫切邏輯的InvocationHandler實(shí)例創(chuàng)建代理實(shí)例
ForumService proxy = (ForumService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
handler);
//④調(diào)用代理實(shí)例
proxy.removeForum(10);
proxy.removeTopic(1012);
}
}
上面的代碼完成業(yè)務(wù)類(lèi)代碼和橫切代碼的編織工作并生成了代理實(shí)例。在②處,我們讓PerformanceHandler將性能監(jiān)視橫切邏輯編織到ForumService實(shí)例中,然后在③處,通過(guò)Proxy的newProxyInstance()靜態(tài)方法為編織了業(yè)務(wù)類(lèi)邏輯和性能監(jiān)視邏輯的handler創(chuàng)建一個(gè)符合ForumService接口的代理實(shí)例。該方法的第一個(gè)入?yún)轭?lèi)加載器;第二個(gè)入?yún)閯?chuàng)建代理實(shí)例所需要實(shí)現(xiàn)的一組接口;第三個(gè)參數(shù)是整合了業(yè)務(wù)邏輯和橫切邏輯的編織器對(duì)象。
按照③處的設(shè)置方式,這個(gè)代理實(shí)例實(shí)現(xiàn)了目標(biāo)業(yè)務(wù)類(lèi)的所有接口,即Forum ServiceImpl的ForumService接口。這樣,我們就可以按照調(diào)用ForumService接口實(shí)例相同的方式調(diào)用代理實(shí)例,如④所示。運(yùn)行以上的代碼,輸出以下信息:
引用
begin monitor...
模擬刪除Forum記錄:10
end monitor...
com.baobaotao.proxy.ForumServiceImpl.removeForum花費(fèi)47毫秒。
begin monitor...
模擬刪除Topic記錄:1012
end monitor...
com.baobaotao.proxy.ForumServiceImpl.removeTopic花費(fèi)26毫秒。
我們發(fā)現(xiàn),程序的運(yùn)行效果和直接在業(yè)務(wù)類(lèi)中編寫(xiě)性能監(jiān)視邏輯的效果一致,但是在這里,原來(lái)分散的橫切邏輯代碼已經(jīng)被我們抽取到PerformanceHandler中。當(dāng)其他業(yè)務(wù)類(lèi)(如UserService、SystemService等)的業(yè)務(wù)方法也需要使用性能監(jiān)視時(shí),我們只要按照代碼清單6-7相似的方式,分別為它們創(chuàng)建代理對(duì)象就可以了。下面,我們通過(guò)時(shí)序圖描述通過(guò)創(chuàng)建代理對(duì)象進(jìn)行業(yè)務(wù)方法調(diào)用的整體邏輯,以進(jìn)一步認(rèn)識(shí)代理對(duì)象的本質(zhì),如圖6-3所示。
我們?cè)谏蠄D中使用虛線的方式對(duì)通過(guò)Proxy創(chuàng)建的ForumService代理實(shí)例加以凸顯,F(xiàn)orumService代理實(shí)例內(nèi)部利用PerformaceHandler整合橫切邏輯和業(yè)務(wù)邏輯。調(diào)用者調(diào)用代理對(duì)象的removeForum()和removeTopic()方法時(shí),上圖的內(nèi)部調(diào)用時(shí)序清晰地告訴我們實(shí)際上所發(fā)生的一切。
CGLib動(dòng)態(tài)代理 使用JDK創(chuàng)建代理有一個(gè)限制,即它只能為接口創(chuàng)建代理實(shí)例,這一點(diǎn)我們可從Proxy的接口newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)的方法簽名中就看得很清楚:第二個(gè)入?yún)nterfaces就是需要代理實(shí)例實(shí)現(xiàn)的接口列表。雖然面向接口編程的思想被很多大師級(jí)人物(包括Rod Johnson)推崇,但在實(shí)際開(kāi)發(fā)中,許多開(kāi)發(fā)者也對(duì)此深感困惑:難道對(duì)一個(gè)簡(jiǎn)單業(yè)務(wù)表的操作也需要老老實(shí)實(shí)地創(chuàng)建5個(gè)類(lèi)(領(lǐng)域?qū)ο箢?lèi)、Dao接口,Dao實(shí)現(xiàn)類(lèi),Service接口和Service實(shí)現(xiàn)類(lèi))嗎?難道不能直接通過(guò)實(shí)現(xiàn)類(lèi)構(gòu)建程序嗎?對(duì)于這個(gè)問(wèn)題,我們很難給出一個(gè)孰好孰劣的準(zhǔn)確判斷,但我們確實(shí)發(fā)現(xiàn)有很多不使用接口的項(xiàng)目也取得了非常好的效果(包括大家所熟悉的SpringSide開(kāi)源項(xiàng)目)。
對(duì)于沒(méi)有通過(guò)接口定義業(yè)務(wù)方法的類(lèi),如何動(dòng)態(tài)創(chuàng)建代理實(shí)例呢?JDK的代理技術(shù)顯然已經(jīng)黔驢技窮,CGLib作為一個(gè)替代者,填補(bǔ)了這個(gè)空缺。
CGLib采用非常底層的字節(jié)碼技術(shù),可以為一個(gè)類(lèi)創(chuàng)建子類(lèi),并在子類(lèi)中采用方法攔截的技術(shù)攔截所有父類(lèi)方法的調(diào)用,并順勢(shì)織入橫切邏輯。下面,我們采用CGLib技術(shù),編寫(xiě)一個(gè)可以為任何類(lèi)創(chuàng)建織入性能監(jiān)視橫切邏輯代理對(duì)象的代理創(chuàng)建器,如代碼清單 6-8所示:
代碼清單6-8 CglibProxy
- package com.baobaotao.proxy;
- import java.lang.reflect.Method;
- import net.sf.cglib.proxy.Enhancer;
- import net.sf.cglib.proxy.MethodInterceptor;
- import net.sf.cglib.proxy.MethodProxy;
-
- public class CglibProxy implements MethodInterceptor {
- private Enhancer enhancer = new Enhancer();
- public Object getProxy(Class clazz) {
- enhancer.setSuperclass(clazz);
- enhancer.setCallback(this);
- return enhancer.create();
-
- }
-
-
- public Object intercept(Object obj, Method method, Object[] args,
- MethodProxy proxy) throws Throwable {
- PerformanceMonitor.begin(obj.getClass().getName()+"."+method. getName());
- Object result=proxy.invokeSuper(obj, args); ③-2
- PerformanceMonitor.end();
- return result;
- }
- }
package com.baobaotao.proxy;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CglibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class clazz) {
enhancer.setSuperclass(clazz); //① 設(shè)置需要?jiǎng)?chuàng)建子類(lèi)的類(lèi)
enhancer.setCallback(this);
return enhancer.create(); //②通過(guò)字節(jié)碼技術(shù)動(dòng)態(tài)創(chuàng)建子類(lèi)實(shí)例
}
//③攔截父類(lèi)所有方法的調(diào)用
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
PerformanceMonitor.begin(obj.getClass().getName()+"."+method. getName());//③-1
Object result=proxy.invokeSuper(obj, args); ③-2
PerformanceMonitor.end();//③-1通過(guò)代理類(lèi)調(diào)用父類(lèi)中的方法
return result;
}
}
在上面代碼中,用戶(hù)可以通過(guò)getProxy(Class clazz)為一個(gè)類(lèi)創(chuàng)建動(dòng)態(tài)代理對(duì)象,該代理對(duì)象通過(guò)擴(kuò)展clazz創(chuàng)建代理對(duì)象。在這個(gè)代理對(duì)象中,我們織入性能監(jiān)視的橫切邏輯(③-1)。intercept(Object obj, Method method, Object[] args,MethodProxy proxy)是CGLib定義的Interceptor接口的方法,它攔截所有目標(biāo)類(lèi)方法的調(diào)用,obj表示目標(biāo)類(lèi)的實(shí)例;method為目標(biāo)類(lèi)方法的反射對(duì)象;args為方法的動(dòng)態(tài)入?yún)ⅲ欢鴓roxy為代理類(lèi)實(shí)例。
下面,我們通過(guò)CglibProxy為ForumServiceImpl類(lèi)創(chuàng)建代理對(duì)象,并測(cè)試代理對(duì)象的方法,如代碼清單6-9所示:
代碼清單6-9 TestForumService:測(cè)試Cglib創(chuàng)建的代理類(lèi)
- package com.baobaotao.proxy;
- import java.lang.reflect.Proxy;
- public class TestForumService {
- public static void main(String[] args) {
- CglibProxy proxy = new CglibProxy();
- ForumServiceImpl forumService = ①
- (ForumServiceImpl )proxy.getProxy(ForumServiceImpl.class);
- forumService.removeForum(10);
- forumService.removeTopic(1023);
- }
- }
package com.baobaotao.proxy;
import java.lang.reflect.Proxy;
public class TestForumService {
public static void main(String[] args) {
CglibProxy proxy = new CglibProxy();
ForumServiceImpl forumService = ①
(ForumServiceImpl )proxy.getProxy(ForumServiceImpl.class);
forumService.removeForum(10);
forumService.removeTopic(1023);
}
}
在①中,我們通過(guò)CglibProxy為ForumServiceImpl動(dòng)態(tài)創(chuàng)建了一個(gè)織入性能監(jiān)視邏輯的代理對(duì)象,并調(diào)用代理類(lèi)的業(yè)務(wù)方法。運(yùn)行上面的代碼,輸入以下信息:
引用
begin monitor...
模擬刪除Forum記錄:10
end monitor...
com.baobaotao.proxy.ForumServiceImpl$$EnhancerByCGLIB$$2a9199c0.removeForum花費(fèi)47毫秒。
begin monitor...
模擬刪除Topic記錄:1023
end monitor...
com.baobaotao.proxy.ForumServiceImpl$$EnhancerByCGLIB$$2a9199c0.removeTopic花費(fèi)16毫秒。
觀察以上的輸出,除了發(fā)現(xiàn)兩個(gè)業(yè)務(wù)方法中都織入了性能監(jiān)控的邏輯外,我們還發(fā)現(xiàn)代理類(lèi)的名字是com.baobaotao.proxy.ForumServiceImpl$$EnhancerByCGLIB$$2a9199c0,這個(gè)特殊的類(lèi)就是CGLib為ForumServiceImpl動(dòng)態(tài)創(chuàng)建的子類(lèi)。
代理知識(shí)小結(jié) Spring AOP的底層就是通過(guò)使用JDK動(dòng)態(tài)代理或CGLib動(dòng)態(tài)代理技術(shù)為目標(biāo)Bean織入橫切邏輯。在這里,我們對(duì)前面兩節(jié)動(dòng)態(tài)創(chuàng)建代理對(duì)象作一個(gè)小結(jié)。
我們雖然通過(guò)PerformanceHandler或CglibProxy實(shí)現(xiàn)了性能監(jiān)視橫切邏輯的動(dòng)態(tài)織入,但這種實(shí)現(xiàn)方式存在三個(gè)明顯需要改進(jìn)的地方:
1)目標(biāo)類(lèi)的所有方法都添加了性能監(jiān)視橫切邏輯,而有時(shí),這并不是我們所期望的,我們可能只希望對(duì)業(yè)務(wù)類(lèi)中的某些特定方法添加橫切邏輯;
2)我們通過(guò)硬編碼的方式指定了織入橫切邏輯的織入點(diǎn),即在目標(biāo)類(lèi)業(yè)務(wù)方法的開(kāi)始和結(jié)束前織入代碼;
3)我們手工編寫(xiě)代理實(shí)例的創(chuàng)建過(guò)程,為不同類(lèi)創(chuàng)建代理時(shí),需要分別編寫(xiě)相應(yīng)的創(chuàng)建代碼,無(wú)法做到通用。
以上三個(gè)問(wèn)題,在AOP中占用重要的地位,因?yàn)镾pring AOP的主要工作就是圍繞以上三點(diǎn)展開(kāi):Spring AOP通過(guò)Pointcut(切點(diǎn))指定在哪些類(lèi)的哪些方法上織入橫切邏輯,通過(guò)Advice(增強(qiáng))描述橫切邏輯和方法的具體織入點(diǎn)(方法前、方法后、方法的兩端等)。此外,Spring通過(guò)Advisor(切面)將Pointcut和Advice兩者組裝起來(lái)。有了Advisor的信息,Spring就可以利用JDK或CGLib的動(dòng)態(tài)代理技術(shù)采用統(tǒng)一的方式為目標(biāo)Bean創(chuàng)建織入切面的代理對(duì)象了。