Spring AOP 使用了兩種代理機(jī)制:
- 基于 JDK 的動(dòng)態(tài)代理(接口代理)。
- 基于 CGLib 的動(dòng)態(tài)代理(類代理)。
1 訂單服務(wù)實(shí)例
假設(shè)有這樣的一個(gè)訂單服務(wù),它提供新增訂單的方法,我們需要對(duì)這個(gè)方法的性能做監(jiān)控。
OrderService:
public interface OrderService {
void add();
}
OrderServiceImpl:
public class OrderServiceImpl implements OrderService {
/**
* 新增
*/
public void add() {
PerformanceMonitor.begin("net.deniro.spring4.aop.add");//開啟監(jiān)視
System.out.println("模擬新增訂單");
try {
Thread.currentThread().sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException();
}
PerformanceMonitor.end();//結(jié)束監(jiān)視
}
}
PerformanceMonitor(性能監(jiān)控器):
public class PerformanceMonitor {
//通過 ThreadLocal,保存與調(diào)用線程相關(guān)的性能監(jiān)視信息
private static ThreadLocal<PerformanceRecord> record=new
ThreadLocal<PerformanceRecord>();
/**
* 開啟監(jiān)視
* @param method 需要監(jiān)視的方法
*/
public static void begin(String method) {
System.out.println("開啟監(jiān)視...");
record.set(new PerformanceRecord(method));
}
/**
* 結(jié)束監(jiān)視
*/
public static void end() {
System.out.println("結(jié)束監(jiān)視...");
record.get().print();
}
}
ThreadLocal 可以將非線程安全的類改造為線程安全的類。
PerformanceRecord(性能記錄):
public class PerformanceRecord {
private final String methodName;//方法名稱
private final long begin;//開始時(shí)間
public PerformanceRecord(String method) {
this.methodName = method;
this.begin = System.currentTimeMillis();
}
/**
* 打印性能信息
*/
public void print() {
long end = System.currentTimeMillis();
long elapse = end - begin;
System.out.println(methodName + " 耗費(fèi)時(shí)間:" + elapse + " 毫秒");
}
}
輸出結(jié)果:
開啟監(jiān)視...
模擬新增訂單
結(jié)束監(jiān)視...
net.deniro.spring4.aop.add 耗費(fèi)時(shí)間:50 毫秒
雖然方法添加了性能監(jiān)視功能,但這些性能監(jiān)視代碼侵入了業(yè)務(wù)邏輯。我們可以通過 JDK 與 CGLib 動(dòng)態(tài)代理技術(shù)把這些具有橫切性質(zhì)的代碼動(dòng)態(tài)織入目標(biāo)方法。
2 JDK 動(dòng)態(tài)代理
Java1.3 以后, JAVA 實(shí)現(xiàn)了動(dòng)態(tài)代理技術(shù),允許開發(fā)者在運(yùn)行期創(chuàng)建接口的代理實(shí)例 。
JDK 的動(dòng)態(tài)代理主要涉及 java.lang.reflect 包中的兩個(gè)類: Proxy 和 InvocationHandler。
- InvocationHandler 是接口,可以通過實(shí)現(xiàn)該接口來定義橫切邏輯,并通過反射機(jī)制調(diào)用目標(biāo)類的代碼,動(dòng)態(tài)地將橫切邏輯和業(yè)務(wù)邏輯編織在一起 。
- Proxy 利用 InvocationHandler 動(dòng)態(tài)創(chuàng)建出一個(gè)實(shí)現(xiàn)某一接口的實(shí)例,來生成目標(biāo)類的代理對(duì)象 。
在 OrderServiceImpl 中,新建一個(gè)只包含業(yè)務(wù)邏輯的方法 addByJDKProxy:
/**
* 新增(JDK 代理)
*/
public void addByJDKProxy() {
addOrder();
}
/**
* 新增訂單
*/
private void addOrder() {
System.out.println("模擬新增訂單");
try {
Thread.currentThread().sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException();
}
}
接著創(chuàng)建動(dòng)態(tài)代理類 PerformanceHandler:
public class PerformanceHandler implements InvocationHandler {
private final Object target;//目標(biāo)類
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());
Object obj=method.invoke(target,args);//調(diào)用業(yè)務(wù)類的目標(biāo)方法
PerformanceMonitor.end();
return obj;
}
}
InvocationHandler 接口只定義了一個(gè)方法:public Object invoke(Object proxy, Method method, Object[] args),各參數(shù)說明如下:
| 參數(shù) | 說明 |
|---|---|
| proxy | 最終生成的代理實(shí)例,很少用到。 |
| method | 被代理目標(biāo)實(shí)例的某個(gè)具體方法,通過它可以發(fā)起目標(biāo)實(shí)例方法的反射調(diào)用。 |
| args | 被代理實(shí)例某個(gè)方法的入?yún)ⅲ诜椒ǚ瓷湔{(diào)用時(shí)使用。 |
在構(gòu)造函數(shù)中,我們通過 target 參數(shù)傳入希望被代理的目標(biāo)對(duì)象 。
單元測(cè)試:
//編織目標(biāo)業(yè)務(wù)類與橫切代碼
PerformanceHandler handler=new PerformanceHandler(orderService);
//創(chuàng)建代理實(shí)例
OrderService proxy=(OrderService) Proxy.newProxyInstance(orderService.getClass()
.getClassLoader(),orderService.getClass().getInterfaces(),handler);
proxy.addByJDKProxy();
這里通過 Proxy 的 newProxyInstance() 的靜態(tài)方法為編織了業(yè)務(wù)邏輯和性能監(jiān)視邏輯的 handler 創(chuàng)建了一個(gè)符合 OrderService 接口的代理實(shí)例 。 這個(gè)方法的各參數(shù)說明如下:
| 參數(shù) | 類型 | 說明 |
|---|---|---|
| loader | ClassLoader | 類加載器。 |
| interfaces | Class<?>[] | 創(chuàng)建代理實(shí)例所需要實(shí)現(xiàn)的一組接口。 |
| h | InvocationHandler | 整合了業(yè)務(wù)邏輯和橫切邏輯的編織器對(duì)象。 |

3 CGLib 動(dòng)態(tài)代理
JDK 創(chuàng)建代理時(shí)只能為接口創(chuàng)建代理實(shí)例 。 Proxy 的 newProxyInstance 方法定義為:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
第二個(gè)參數(shù) interfaces 就是需要代理實(shí)例實(shí)現(xiàn)的接口列表 。
所以對(duì)于沒有通用接口定義業(yè)務(wù)方法的類,可以 CGLib 實(shí)現(xiàn)動(dòng)態(tài)代理。
CGLib 采用底層的字節(jié)碼技術(shù),可以為一個(gè)類創(chuàng)建子類,在子類中采用方法攔截技術(shù)來攔截所有父類方法的調(diào)用并順勢(shì)織入橫切邏輯 。
代理類:
public class PerformanceProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class clazz) {
enhancer.setSuperclass(clazz);//設(shè)置父類
enhancer.setCallback(this);
return enhancer.create();//使用字節(jié)碼技術(shù)動(dòng)態(tài)創(chuàng)建子類的實(shí)例
}
/**
* 攔截父類所有方法的調(diào)用
*
* @param o 目標(biāo)類實(shí)例
* @param method 目標(biāo)類方法的反射對(duì)象
* @param args 方法的動(dòng)態(tài)入?yún)? * @param methodProxy 代理類實(shí)例
* @return
* @throws Throwable
*/
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
PerformanceMonitor.begin(o.getClass().getName() + "." + method.getName());
Object result = methodProxy.invokeSuper(o, args);
PerformanceMonitor.end();
return result;
}
}
單元測(cè)試:
PerformanceProxy proxy = new PerformanceProxy();
OrderServiceImpl orderService = (OrderServiceImpl) proxy.getProxy(OrderServiceImpl.class);
orderService.addOrder();
輸出結(jié)果:
開啟監(jiān)視...
模擬新增訂單
結(jié)束監(jiān)視...
net.deniro.spring4.aop.OrderServiceImpl$$EnhancerByCGLIB$$aea908ed.addOrder耗費(fèi)時(shí)間:83 毫秒
代理類名變?yōu)?OrderServiceImpl$$EnhancerByCGLIB$$aea908ed.addOrder 這個(gè)特殊的類就是 CGLib 為 OrderServiceImpl 動(dòng)態(tài)創(chuàng)建的子類 。
Spring AOP 通過切點(diǎn)(Pointcut)來決定需要在哪些類的哪些方法上織入橫切邏輯;接著通過增強(qiáng)(Advice)來描述橫切邏輯和方法的具體織入點(diǎn);然后通過切面(Advisor)把切點(diǎn)與增強(qiáng)裝配起來;最后,利用 JDK 或 CGLib 的動(dòng)態(tài)代理技術(shù)為目標(biāo)類創(chuàng)建已織入切面的代理對(duì)象。
所創(chuàng)建的動(dòng)態(tài)代理對(duì)象的性能,CGLib 是 JDK 的 10 倍;而創(chuàng)建動(dòng)態(tài)代理對(duì)象所花費(fèi)的時(shí)間上,CGLib 卻比 JDK 多花 8 倍的時(shí)間。所以,對(duì)于單例模式或者具有實(shí)例池的代理類,適合采用 CGLib 技術(shù);反之,則適合采用 JDK 技術(shù)。