原文同步發表至個人博客【夜月歸途】
原文鏈接:http://www.guitu18.com/se/java/2018-06-29/18.html
出處: http://www.guitu18.com/
本博客中未標明轉載的文章歸作者 夜月歸途和博客園所有。 歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。
本博客關於Java動態代理相關內容直達鏈接:
Java中的動態代理設計模式是非常經典且非常重要的設計模式之一,在感嘆設計者的天才設計至於,我們想去探究一下這個設計模式是如何來實現的;
著名的spring框架的AOP的原理就是Java的動態代理機制;
在Spring中動態代理是實現有兩種:JDK動態代理和Cglib動態代理,本篇分析的是Cglib動態代理的實現;
JDK動態代理之前已經做過分析了[ JDK動態代理淺析 ],通過案例我們知道了,Java動態代理的強大之處;但是不難看出來使用JDK動態代理也有着它的局限性,JDK動態代理是在JVM內部動態的生成class字節碼對象,但是JDK動態代理只能針對接口進行操作,也就是只能對接口的實現類去進行代理;
現在分析一下另一種常用的動態代理技術:Cglib動態代理;
CGLIB(Code Generation Library)是一個開源項目;
是一個強大的,高性能,高質量的Code生成類庫,它可以在運行期擴展Java類與實現Java接口;
CGLIB包的底層是通過使用一個小而快的字節碼處理框架ASM,來轉換字節碼並生成新的類;
Cglib是針對類來實現代理的,原理是對指定的業務類生成一個子類,並覆蓋其中業務方法實現代理;
所以Cglib可以為無接口的類直接做代理,當然有接口的類也是可以的並無影響。
因為采用的是繼承,所以不能對final修飾的類進行代理;Cglib的使用限制無疑要比JDK動態代理要寬松很多;
廢話不多說,先上案例;
這里說明一下,如果是Spring框架那么無所謂,Spring框架的spring-core.jar包中已經集成了cglib與asm;如果你要單獨使用CGLIB,那么需要導入cglib的jar包和asm相關jar包;
Programmer類:
public class Programmer { public void work(String name) { System.out.println(name + " 正在工作..."); } }
CglibProxyFactory代理類:
public class CglibProxyFactory implements MethodInterceptor { private Object target; public CglibProxyFactory(Object target) { this.target = target; } public Object getProxyInstance() { // 創建 Enhancer 對象 Enhancer enhancer = new Enhancer(); // 設置目標對象的Class enhancer.setSuperclass(target.getClass()); // 設置回調操作,相當於InvocationHandler enhancer.setCallback(this); return enhancer.create(); } @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println("開始事務..."); // 兩種方式執行方法,第二行注釋掉的,和當前行代碼效果相同,下面會分析; Object invoke = method.invoke(target, args); // Object invoke = methodProxy.invokeSuper(proxy, args); System.out.println("提交/回滾事務..."); return invoke; } }
Cglib創建代理的方式非常簡單,是通過Enhancer對象,需要給它set兩個參數:
設置目標對象的Class; 設置回調操作,相當於InvocationHandler,需要實現MethodInterceptor 接口; 之后執行create()方法即可返回目標對象的代理對象;
這里為了方便,直接讓CglibProxyFactory實現了MethodInterceptor 接口,需要實現重寫MethodInterceptor.intercept()方法;
在intercept()方法中,其實跟InvocationHandler.invoke中做的事是一樣的,我們需要調用method.invoke()去執行方法;
在案例中,還有注釋掉的一行:Object invoke = methodProxy.invokeSuper(proxy, args);
這個和上面的 Object invoke = method.invoke(target, args); 是一樣的效果,都是代理執行方法,下面進行說明;
從代碼中看出,intercept()的參數比JDK的invoke()多了一個,前三個參數跟JDK的invoke()方法一樣不多說,第四個參數MethodProxy methodProxy是當前代理對象執行的方法的一個代理,也就是MethodProxy methodProxy是第二個參數Method method的代理;
前面我們說過,Cglib動態代理的機制是對指定的業務類生成一個子類,並覆蓋其中業務方法實現代理;也就是參數一Object proxy實際上是我們的目標對象Object target的一個子類;
我們直接使用 method.invoke(target, args) 執行方法,和使用methodProxy.invokeSuper(proxy, args) 執行是一個什么關系呢;
第一個被代理類target直接調用method執行沒問題,第二個其實是target的子類proxy去掉用父的方法,我第一次看這里也是有點繞哈,但是其實就是這么個關系,這兩個方法執行的效果是一樣的,但是通常我們只使用第一種方式;
測試執行,貼測試代碼:
public class CglibProxyFactoryTest { public static void main(String[] args) { Programmer programmer = new Programmer(); CglibProxyFactory cglibProxyFactory = new CglibProxyFactory(programmer); Programmer instance = (Programmer) cglibProxyFactory.getProxyInstance(); instance.work("夜月歸途"); } }
控制台輸出:
開始事務...
夜月歸途 正在工作...
提交/回滾事務...
看到這里和JDK的動態代理是一樣的,Cglib的的動態代理沒有接口的限制,有接口的類和無接口的類都可以被代理,只要不是final的;
在這里,同樣代碼可以精簡一下:
public class CglibProxyFactory { public static Object getProxyInstance(Object target) { // 創建 Enhancer 對象 Enhancer enhancer = new Enhancer(); // 設置目標對象的Class enhancer.setSuperclass(target.getClass()); // 設置回調操作,相當於InvocationHandler enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println("開始事務..."); // Object invoke1 = method.invoke(target, args); Object invoke2 = methodProxy.invokeSuper(proxy, args); System.out.println("提交/回滾事務..."); return invoke2; } }); return enhancer.create(); } }
以匿名內部類形式創建代理對象,這樣子代碼清爽許多;
尾巴:
- 靜態代理是通過在代碼中顯式定義一個業務實現類一個代理,在代理類中對同名的業務方法進行包裝,用戶通過代理類調用被包裝過的業務方法;
- JDK動態代理是通過接口中的方法名,在動態生成的代理類中調用業務實現類的同名方法;
- CGlib動態代理是通過繼承業務類,生成的動態代理類是業務類的子類,通過重寫業務方法進行代理;
- 如果目標對象有接口,優先使用jdk動態代理,如果目標對象無接口,使用cglib動態代理,如:Spring框架的代理選擇機制就是這樣的;