共計 4999 個字符,預計需要花費 13 分鐘才能閱讀完成。
如何從動態代理實現到 Spring AOP,針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。
丸趣 TV 小編主要講了 Spring Aop 動態代理實現的兩種方式。
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 來實現的??偟膩碚f,反射機制在生成類的過程中比較高效,而 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 的類庫,使用的是繼承機制,是被代理類和代理類繼承的關系,所以代理類是可以賦值給被代理類的,如果被代理類有接口,那么代理類也可以賦值給接口。
關于如何從動態代理實現到 Spring AOP 問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注丸趣 TV 行業資訊頻道了解更多相關知識。