1. Spring AOP
Spring是一個輕型容器,Spring整個系列的最最核心的概念當屬IoC、AOP。可見AOP是Spring框架中的核心之一,在應用中具有非常重要的作用,也是Spring其他元件的基礎。AOP(Aspect Oriented Programming),即面向切面程式設計,可以說是OOP(Object Oriented Programming,物件導向程式設計)的補充和完善。OOP引入封裝、繼承、多型等概念來建立一種物件層次結構,用於模擬公共行為的一個集合。不過OOP允許開發者定義縱向的關係,但並不適合定義橫向的關係,例如日誌功能。
關於AOP的基礎知識,並不是本文的重點,我們主要來看下AOP的核心功能的底層實現機制:動態代理的實現原理。AOP的攔截功能是由java中的動態代理來實現的。在目標類的基礎上增加切面邏輯,生成增強的目標類(該切面邏輯或者在目標類函式執行之前,或者目標類函式執行之後,或者在目標類函式丟擲異常時候執行。不同的切入時機對應不同的Interceptor的種類,如BeforeAdviseInterceptor,AfterAdviseInterceptor以及ThrowsAdviseInterceptor等)。
那麼動態代理是如何實現將切面邏輯(advise)織入到目標類方法中去的呢?下面我們就來詳細介紹並實現AOP中用到的兩種動態代理。
AOP的原始碼中用到了兩種動態代理來實現攔截切入功能:jdk動態代理和cglib動態代理。兩種方法同時存在,各有優劣。jdk動態代理是由java內部的反射機制來實現的,cglib動態代理底層則是藉助asm來實現的。總的來說,反射機制在生成類的過程中比較高效,而asm在生成類之後的相關執行過程中比較高效(可以通過將asm生成的類進行快取,這樣解決asm生成類過程低效問題)。
下面我們分別來示例實現這兩種方法。
2. JDK動態代理
2.1 定義介面與實現類
public interface OrderService {
public void save(UUID orderId, String name);
public void update(UUID orderId, String name);
public String getByName(String name);
}
複製程式碼
上面程式碼定義了一個被攔截物件介面,即橫切關注點。下面程式碼實現被攔截物件介面。
public class OrderServiceImpl implements OrderService {
private String user = null;
public OrderServiceImpl() {
}
public OrderServiceImpl(String user) {
this.setUser(user);
}
//...
@Override
public void save(UUID orderId, String name) {
System.out.println("call save()方法,save:" + name);
}
@Override
public void update(UUID orderId, String name) {
System.out.println("call update()方法");
}
@Override
public String getByName(String name) {
System.out.println("call getByName()方法");
return "aoho";
}
}
複製程式碼
2.2 JDK動態代理類
public class JDKProxy implements InvocationHandler {
//需要代理的目標物件
private Object targetObject;
public Object createProxyInstance(Object targetObject) {
this.targetObject = targetObject;
return Proxy.newProxyInstance(this.targetObject.getClass().getClassLoader(),
this.targetObject.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//被代理物件
OrderServiceImpl bean = (OrderServiceImpl) this.targetObject;
Object result = null;
//切面邏輯(advise),此處是在目標類程式碼執行之前
System.out.println("---before invoke----");
if (bean.getUser() != null) {
result = method.invoke(targetObject, args);
}
System.out.println("---after invoke----");
return result;
}
//...
}
複製程式碼
上述程式碼實現了動態代理類JDKProxy,實現InvocationHandler介面,並且實現介面中的invoke方法。當客戶端呼叫代理物件的業務方法時,代理物件執行invoke方法,invoke方法把呼叫委派給targetObject,相當於呼叫目標物件的方法,在invoke方法委派前判斷許可權,實現方法的攔截。
2.3 測試
public class AOPTest {
public static void main(String[] args) {
JDKProxy factory = new JDKProxy();
//Proxy為InvocationHandler實現類動態建立一個符合某一介面的代理例項
OrderService orderService = (OrderService) factory.createProxyInstance(new OrderServiceImpl("aoho"));
//由動態生成的代理物件來orderService 代理執行程式
orderService.save(UUID.randomUUID(), "aoho");
}
}
複製程式碼
結果如下:
---before invoke----
call save()方法,save:aoho
---after invoke----
複製程式碼
3. CGLIB位元組碼生成
3.1 要代理的類
CGLIB既可以對介面的類生成代理,也可以針對類生成代理。示例中,實現對類的代理。
public class OrderManager {
private String user = null;
public OrderManager() {
}
public OrderManager(String user) {
this.setUser(user);
}
//...
public void save(UUID orderId, String name) {
System.out.println("call save()方法,save:" + name);
}
public void update(UUID orderId, String name) {
System.out.println("call update()方法");
}
public String getByName(String name) {
System.out.println("call getByName()方法");
return "aoho";
}
}
複製程式碼
該類的實現和上面的介面實現一樣,為了保持統一。
3.2 CGLIB動態代理類
public class CGLibProxy implements MethodInterceptor {
// CGLib需要代理的目標物件
private Object targetObject;
public Object createProxyObject(Object obj) {
this.targetObject = obj;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(obj.getClass());
//回撥方法的引數為代理類物件CglibProxy,最後增強目標類呼叫的是代理類物件CglibProxy中的intercept方法
enhancer.setCallback(this);
//增強後的目標類
Object proxyObj = enhancer.create();
// 返回代理物件
return proxyObj;
}
@Override
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
Object obj = null;
//切面邏輯(advise),此處是在目標類程式碼執行之前
System.out.println("---before intercept----");
obj = method.invoke(targetObject, args);
System.out.println("---after intercept----");
return obj;
}
}
複製程式碼
上述實現了建立子類的方法與代理的方法。getProxy(SuperClass.class)方法通過入參即父類的位元組碼,擴充套件父類的class來建立代理物件。intercept()方法攔截所有目標類方法的呼叫,obj表示目標類的例項,method為目標類方法的反射物件,args為方法的動態入參,methodProxy為代理類例項。method.invoke(targetObject, args)通過代理類呼叫父類中的方法。
3.3 測試
public class AOPTest {
public static void main(String[] args) {
OrderManager order = (OrderManager) new CGLibProxy().createProxyObject(new OrderManager("aoho"));
order.save(UUID.randomUUID(), "aoho");
}
複製程式碼
結果如下:
---before intercept----
call save()方法,save:aoho
---after intercept----
複製程式碼
4. 總結
本文主要講了Spring Aop動態代理實現的兩種方式,並分別介紹了其優缺點。jdk動態代理的應用前提是目標類基於統一的介面。如果沒有該前提,jdk動態代理不能應用。由此可以看出,jdk動態代理有一定的侷限性,cglib這種第三方類庫實現的動態代理應用更加廣泛,且在效率上更有優勢。
JDK動態代理機制是委託機制,不需要以來第三方的庫,只要要JDK環境就可以進行代理,動態實現介面類,在動態生成的實現類裡面委託為hanlder去呼叫原始實現類方法;CGLib 必須依賴於CGLib的類庫,使用的是繼承機制,是被代理類和代理類繼承的關係,所以代理類是可以賦值給被代理類的,如果被代理類有介面,那麼代理類也可以賦值給介面。