Java 動態(tài)代理詳解

后端Java編程

創(chuàng)建時間:2018-12-21 01:46

字數(shù):5,622閱讀:1706評論:

動態(tài)代理在Java中有著廣泛的應用,比如Spring AOP、Hibernate數(shù)據查詢、測試框架的后端mock、RPC遠程調用、Java注解對象獲取、日志、用戶鑒權、全局性異常處理、性能監(jiān)控,甚至事務處理等。

本文主要介紹Java中兩種常見的動態(tài)代理方式:JDK原生動態(tài)代理和CGLIB動態(tài)代理。

由于Java動態(tài)代理與java反射機制關系緊密,請讀者確保已經了解了Java反射機制,可參考上一篇文章《Java反射機制詳解

代理模式

本文將介紹的Java動態(tài)代理與設計模式中的代理模式有關,什么是代理模式呢?

代理模式:給某一個對象提供一個代理,并由代理對象來控制對真實對象的訪問。代理模式是一種結構型設計模式。

代理模式角色分為 3 種:

Subject(抽象主題角色):定義代理類和真實主題的公共對外方法,也是代理類代理真實主題的方法;

RealSubject(真實主題角色):真正實現(xiàn)業(yè)務邏輯的類;

Proxy(代理主題角色):用來代理和封裝真實主題;

代理模式的結構比較簡單,其核心是代理類,為了讓客戶端能夠一致性地對待真實對象和代理對象,在代理模式中引入了抽象層

代理模式類圖

代理模式按照職責(使用場景)來分類,至少可以分為以下幾類:1、遠程代理。 2、虛擬代理。 3、Copy-on-Write 代理。 4、保護(Protect or Access)代理。 5、Cache代理。 6、防火墻(Firewall)代理。 7、同步化(Synchronization)代理。 8、智能引用(Smart Reference)代理等等。

如果根據字節(jié)碼的創(chuàng)建時機來分類,可以分為靜態(tài)代理和動態(tài)代理:

所謂靜態(tài)也就是在程序運行前就已經存在代理類的字節(jié)碼文件,代理類和真實主題角色的關系在運行前就確定了。

而動態(tài)代理的源碼是在程序運行期間由JVM根據反射等機制動態(tài)的生成,所以在運行前并不存在代理類的字節(jié)碼文件

靜態(tài)代理

我們先通過實例來學習靜態(tài)代理,然后理解靜態(tài)代理的缺點,再來學習本文的主角:動態(tài)代理

編寫一個接口 UserService ,以及該接口的一個實現(xiàn)類 UserServiceImpl

publicinterfaceUserService{publicvoidselect();publicvoidupdate();}publicclassUserServiceImplimplementsUserService{publicvoidselect(){? ? ? ? ? System.out.println("查詢 selectById");? ? }publicvoidupdate(){? ? ? ? System.out.println("更新 update");? ? }}

我們將通過靜態(tài)代理對 UserServiceImpl 進行功能增強,在調用select和update之前記錄一些日志。寫一個代理類 UserServiceProxy,代理類需要實現(xiàn) UserService

publicclassUserServiceProxyimplementsUserService{privateUserService target;// 被代理的對象publicUserServiceProxy(UserService target){this.target = target;? ? }publicvoidselect(){? ? ? ? before();? ? ? ? target.select();// 這里才實際調用真實主題角色的方法after();? ? }publicvoidupdate(){? ? ? ? before();? ? ? ? target.update();// 這里才實際調用真實主題角色的方法after();? ? }privatevoidbefore(){// 在執(zhí)行方法之前執(zhí)行System.out.println(String.format("log start time [%s] ",newDate()));? ? }privatevoidafter(){// 在執(zhí)行方法之后執(zhí)行System.out.println(String.format("log end time [%s] ",newDate()));? ? }}

客戶端測試

publicclassClient1{publicstaticvoidmain(String[] args){? ? ? ? UserService userServiceImpl =newUserServiceImpl();? ? ? ? UserService proxy =newUserServiceProxy(userServiceImpl);? ? ? ? proxy.select();? ? ? ? proxy.update();? ? }}

輸出

logstarttime[ThuDec2014:13:25CST2018] 查詢 selectByIdlogendtime[ThuDec2014:13:25CST2018]logstarttime[ThuDec2014:13:25CST2018] 更新updatelogendtime[ThuDec2014:13:25CST2018]

通過靜態(tài)代理,我們達到了功能增強的目的,而且沒有侵入原代碼,這是靜態(tài)代理的一個優(yōu)點。

靜態(tài)代理的缺點

雖然靜態(tài)代理實現(xiàn)簡單,且不侵入原代碼,但是,當場景稍微復雜一些的時候,靜態(tài)代理的缺點也會暴露出來。

1、 當需要代理多個類的時候,由于代理對象要實現(xiàn)與目標對象一致的接口,有兩種方式:

只維護一個代理類,由這個代理類實現(xiàn)多個接口,但是這樣就導致代理類過于龐大

新建多個代理類,每個目標對象對應一個代理類,但是這樣會產生過多的代理類

2、 當接口需要增加、刪除、修改方法的時候,目標對象與代理類都要同時修改,不易維護。

如何改進?

當然是讓代理類動態(tài)的生成啦,也就是動態(tài)代理。

為什么類可以動態(tài)的生成?

這就涉及到Java虛擬機的類加載機制了,推薦翻看《深入理解Java虛擬機》7.3節(jié) 類加載的過程。

Java虛擬機類加載過程主要分為五個階段:加載、驗證、準備、解析、初始化。其中加載階段需要完成以下3件事情:

通過一個類的全限定名來獲取定義此類的二進制字節(jié)流

將這個字節(jié)流所代表的靜態(tài)存儲結構轉化為方法區(qū)的運行時數(shù)據結構

在內存中生成一個代表這個類的java.lang.Class對象,作為方法區(qū)這個類的各種數(shù)據訪問入口

由于虛擬機規(guī)范對這3點要求并不具體,所以實際的實現(xiàn)是非常靈活的,關于第1點,獲取類的二進制字節(jié)流(class字節(jié)碼)就有很多途徑:

從ZIP包獲取,這是JAR、EAR、WAR等格式的基礎

從網絡中獲取,典型的應用是 Applet

運行時計算生成,這種場景使用最多的是動態(tài)代理技術,在 java.lang.reflect.Proxy 類中,就是用了 ProxyGenerator.generateProxyClass 來為特定接口生成形式為*$Proxy的代理類的二進制字節(jié)流

由其它文件生成,典型應用是JSP,即由JSP文件生成對應的Class類

從數(shù)據庫中獲取等等

所以,動態(tài)代理就是想辦法,根據接口或目標對象,計算出代理類的字節(jié)碼,然后再加載到JVM中使用。但是如何計算?如何生成?情況也許比想象的復雜得多,我們需要借助現(xiàn)有的方案。

常見的字節(jié)碼操作類庫

這里有一些介紹:https://java-source.net/open-source/bytecode-libraries

Apache BCEL (Byte Code Engineering Library):是Java classworking廣泛使用的一種框架,它可以深入到JVM匯編語言進行類操作的細節(jié)。

ObjectWeb ASM:是一個Java字節(jié)碼操作框架。它可以用于直接以二進制形式動態(tài)生成stub根類或其他代理類,或者在加載時動態(tài)修改類。

CGLIB(Code Generation Library):是一個功能強大,高性能和高質量的代碼生成庫,用于擴展JAVA類并在運行時實現(xiàn)接口。

Javassist:是Java的加載時反射系統(tǒng),它是一個用于在Java中編輯字節(jié)碼的類庫; 它使Java程序能夠在運行時定義新類,并在JVM加載之前修改類文件。

實現(xiàn)動態(tài)代理的思考方向

為了讓生成的代理類與目標對象(真實主題角色)保持一致性,從現(xiàn)在開始將介紹以下兩種最常見的方式:

通過實現(xiàn)接口的方式 -> JDK動態(tài)代理

通過繼承類的方式 -> CGLIB動態(tài)代理

注:使用ASM對使用者要求比較高,使用Javassist會比較麻煩

JDK動態(tài)代理

JDK動態(tài)代理主要涉及兩個類:java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler,我們仍然通過案例來學習

編寫一個調用邏輯處理器 LogHandler 類,提供日志增強功能,并實現(xiàn) InvocationHandler 接口;在 LogHandler 中維護一個目標對象,這個對象是被代理的對象(真實主題角色);在invoke方法中編寫方法調用的邏輯處理

importjava.lang.reflect.InvocationHandler;importjava.lang.reflect.Method;importjava.util.Date;publicclassLogHandlerimplementsInvocationHandler{? ? Object target;// 被代理的對象,實際的方法執(zhí)行者publicLogHandler(Object target){this.target = target;? ? }@OverridepublicObjectinvoke(Object proxy, Method method, Object[] args)throwsThrowable{? ? ? ? before();? ? ? ? Object result = method.invoke(target, args);// 調用 target 的 method 方法after();returnresult;// 返回方法的執(zhí)行結果}// 調用invoke方法之前執(zhí)行privatevoidbefore(){? ? ? ? System.out.println(String.format("log start time [%s] ",newDate()));? ? }// 調用invoke方法之后執(zhí)行privatevoidafter(){? ? ? ? System.out.println(String.format("log end time [%s] ",newDate()));? ? }}

編寫客戶端,獲取動態(tài)生成的代理類的對象須借助 Proxy 類的 newProxyInstance 方法,具體步驟可見代碼和注釋

importproxy.UserService;importproxy.UserServiceImpl;importjava.lang.reflect.InvocationHandler;importjava.lang.reflect.Proxy;publicclassClient2{publicstaticvoidmain(String[] args)throwsIllegalAccessException, InstantiationException{// 設置變量可以保存動態(tài)代理類,默認名稱以 $Proxy0 格式命名// System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");// 1. 創(chuàng)建被代理的對象,UserService接口的實現(xiàn)類UserServiceImpl userServiceImpl =newUserServiceImpl();// 2. 獲取對應的 ClassLoaderClassLoader classLoader = userServiceImpl.getClass().getClassLoader();// 3. 獲取所有接口的Class,這里的UserServiceImpl只實現(xiàn)了一個接口UserService,Class[] interfaces = userServiceImpl.getClass().getInterfaces();// 4. 創(chuàng)建一個將傳給代理類的調用請求處理器,處理所有的代理對象上的方法調用//? ? 這里創(chuàng)建的是一個自定義的日志處理器,須傳入實際的執(zhí)行對象 userServiceImplInvocationHandler logHandler =newLogHandler(userServiceImpl);/*

? ? ? ? ? 5.根據上面提供的信息,創(chuàng)建代理對象 在這個過程中,

? ? ? ? ? ? ? a.JDK會通過根據傳入的參數(shù)信息動態(tài)地在內存中創(chuàng)建和.class 文件等同的字節(jié)碼

? ? ? ? ? ? ? b.然后根據相應的字節(jié)碼轉換成對應的class,

? ? ? ? ? ? ? c.然后調用newInstance()創(chuàng)建代理實例

? ? ? ? */UserService proxy = (UserService) Proxy.newProxyInstance(classLoader, interfaces, logHandler);// 調用代理的方法proxy.select();? ? ? ? proxy.update();// 保存JDK動態(tài)代理生成的代理類,類名保存為 UserServiceProxy// ProxyUtils.generateClassFile(userServiceImpl.getClass(), "UserServiceProxy");}}

運行結果

logstarttime[ThuDec2016:55:19CST2018] 查詢 selectByIdlogendtime[ThuDec2016:55:19CST2018]logstarttime[ThuDec2016:55:19CST2018] 更新updatelogendtime[ThuDec2016:55:19CST2018]

InvocationHandler 和 Proxy 的主要方法介紹如下:

java.lang.reflect.InvocationHandler

Object invoke(Object proxy, Method method, Object[] args)定義了代理對象調用方法時希望執(zhí)行的動作,用于集中處理在動態(tài)代理類對象上的方法調用

java.lang.reflect.Proxy

static InvocationHandler getInvocationHandler(Object proxy)用于獲取指定代理對象所關聯(lián)的調用處理器

static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)返回指定接口的代理類

static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)構造實現(xiàn)指定接口的代理類的一個新實例,所有方法會調用給定處理器對象的 invoke 方法

static boolean isProxyClass(Class<?> cl)返回 cl 是否為一個代理類

代理類的調用過程

生成的代理類到底長什么樣子呢?借助下面的工具類,把代理類保存下來再探個究竟

(通過設置環(huán)境變量sun.misc.ProxyGenerator.saveGeneratedFiles=true也可以保存代理類)

importsun.misc.ProxyGenerator;importjava.io.FileOutputStream;importjava.io.IOException;publicclassProxyUtils{/**

? ? * 將根據類信息動態(tài)生成的二進制字節(jié)碼保存到硬盤中,默認的是clazz目錄下

? ? * params: clazz 需要生成動態(tài)代理類的類

? ? * proxyName: 為動態(tài)生成的代理類的名稱

? ? */publicstaticvoidgenerateClassFile(Class clazz, String proxyName){// 根據類信息和提供的代理類名稱,生成字節(jié)碼byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());? ? ? ? String paths = clazz.getResource(".").getPath();? ? ? ? System.out.println(paths);? ? ? ? FileOutputStream out =null;try{//保留到硬盤中out =newFileOutputStream(paths + proxyName +".class");? ? ? ? ? ? out.write(classFile);? ? ? ? ? ? out.flush();? ? ? ? }catch(Exception e) {? ? ? ? ? ? e.printStackTrace();? ? ? ? }finally{try{? ? ? ? ? ? ? ? out.close();? ? ? ? ? ? }catch(IOException e) {? ? ? ? ? ? ? ? e.printStackTrace();? ? ? ? ? ? }? ? ? ? }? ? }}

然后在 Client2 測試類的main的最后面加入一行代碼

// 保存JDK動態(tài)代理生成的代理類,類名保存為 UserServiceProxyProxyUtils.generateClassFile(userServiceImpl.getClass(),"UserServiceProxy");

IDEA 再次運行之后就可以在 target 的類路徑下找到 UserServiceProxy.class,雙擊后IDEA的反編譯插件會將該二進制class文件

JDK 動態(tài)代理生成的代理類

UserServiceProxy 的代碼如下所示:

importjava.lang.reflect.InvocationHandler;importjava.lang.reflect.Method;importjava.lang.reflect.Proxy;importjava.lang.reflect.UndeclaredThrowableException;importproxy.UserService;publicfinalclassUserServiceProxyextendsProxyimplementsUserService{privatestaticMethod m1;privatestaticMethod m2;privatestaticMethod m4;privatestaticMethod m0;privatestaticMethod m3;publicUserServiceProxy(InvocationHandler var1)throws{super(var1);? ? }publicfinalbooleanequals(Object var1)throws{// 省略...}publicfinalStringtoString()throws{// 省略...}publicfinalvoidselect()throws{try{super.h.invoke(this, m4, (Object[])null);? ? ? ? }catch(RuntimeException | Error var2) {throwvar2;? ? ? ? }catch(Throwable var3) {thrownewUndeclaredThrowableException(var3);? ? ? ? }? ? }publicfinalinthashCode()throws{// 省略...}publicfinalvoidupdate()throws{try{super.h.invoke(this, m3, (Object[])null);? ? ? ? }catch(RuntimeException | Error var2) {throwvar2;? ? ? ? }catch(Throwable var3) {thrownewUndeclaredThrowableException(var3);? ? ? ? }? ? }static{try{? ? ? ? ? ? m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));? ? ? ? ? ? m2 = Class.forName("java.lang.Object").getMethod("toString");? ? ? ? ? ? m4 = Class.forName("proxy.UserService").getMethod("select");? ? ? ? ? ? m0 = Class.forName("java.lang.Object").getMethod("hashCode");? ? ? ? ? ? m3 = Class.forName("proxy.UserService").getMethod("update");? ? ? ? }catch(NoSuchMethodException var2) {thrownewNoSuchMethodError(var2.getMessage());? ? ? ? }catch(ClassNotFoundException var3) {thrownewNoClassDefFoundError(var3.getMessage());? ? ? ? }? ? }}

從 UserServiceProxy 的代碼中我們可以發(fā)現(xiàn):

UserServiceProxy 繼承了 Proxy 類,并且實現(xiàn)了被代理的所有接口,以及equals、hashCode、toString等方法

由于 UserServiceProxy 繼承了 Proxy 類,所以每個代理類都會關聯(lián)一個 InvocationHandler 方法調用處理器

類和所有方法都被public final修飾,所以代理類只可被使用,不可以再被繼承

每個方法都有一個 Method 對象來描述,Method 對象在static靜態(tài)代碼塊中創(chuàng)建,以m + 數(shù)字的格式命名

調用方法的時候通過super.h.invoke(this, m1, (Object[])null);調用,其中的super.h.invoke實際上是在創(chuàng)建代理的時候傳遞給Proxy.newProxyInstance的 LogHandler 對象,它繼承 InvocationHandler 類,負責實際的調用處理邏輯

而 LogHandler 的 invoke 方法接收到 method、args 等參數(shù)后,進行一些處理,然后通過反射讓被代理的對象 target 執(zhí)行方法

@OverridepublicObjectinvoke(Object proxy, Method method, Object[] args)throwsThrowable{? ? ? ? before();? ? ? ? Object result = method.invoke(target, args);// 調用 target 的 method 方法after();returnresult;// 返回方法的執(zhí)行結果}

JDK動態(tài)代理執(zhí)行方法調用的過程簡圖如下:

JDK動態(tài)代理執(zhí)行方法調用過程

代理類的調用過程相信大家都明了了,而關于Proxy的源碼解析,還請大家另外查閱其他文章或者直接看源碼

CGLIB動態(tài)代理

maven引入CGLIB包,然后編寫一個UserDao類,它沒有接口,只有兩個方法,select() 和 update()

publicclassUserDao{publicvoidselect(){? ? ? ? System.out.println("UserDao 查詢 selectById");? ? }publicvoidupdate(){? ? ? ? System.out.println("UserDao 更新 update");? ? }}

編寫一個 LogInterceptor ,繼承了 MethodInterceptor,用于方法的攔截回調

importjava.lang.reflect.Method;importjava.util.Date;publicclassLogInterceptorimplementsMethodInterceptor{/**? ? *@paramobject 表示要進行增強的對象? ? *@parammethod 表示攔截的方法? ? *@paramobjects 數(shù)組表示參數(shù)列表,基本數(shù)據類型需要傳入其包裝類型,如int-->Integer、long-Long、double-->Double? ? *@parammethodProxy 表示對方法的代理,invokeSuper方法表示對被代理對象方法的調用? ? *@return執(zhí)行結果? ? *@throwsThrowable? ? */@OverridepublicObjectintercept(Object object, Method method, Object[] objects, MethodProxy methodProxy)throwsThrowable{? ? ? ? before();? ? ? ? Object result = methodProxy.invokeSuper(object, objects);// 注意這里是調用 invokeSuper 而不是 invoke,否則死循環(huán),methodProxy.invokesuper執(zhí)行的是原始類的方法,method.invoke執(zhí)行的是子類的方法after();returnresult;? ? }privatevoidbefore(){? ? ? ? System.out.println(String.format("log start time [%s] ",newDate()));? ? }privatevoidafter(){? ? ? ? System.out.println(String.format("log end time [%s] ",newDate()));? ? }}

測試

importnet.sf.cglib.proxy.Enhancer;publicclassCglibTest{publicstaticvoidmain(String[] args){? ? ? ? DaoProxy daoProxy =newDaoProxy();? ? ? ? Enhancer enhancer =newEnhancer();? ? ? ? enhancer.setSuperclass(Dao.class);// 設置超類,cglib是通過繼承來實現(xiàn)的enhancer.setCallback(daoProxy);? ? ? ? Dao dao = (Dao)enhancer.create();// 創(chuàng)建代理類dao.update();? ? ? ? dao.select();? ? }}

運行結果

logstarttime[FriDec2100:06:40CST2018] UserDao 查詢 selectByIdlogendtime[FriDec2100:06:40CST2018]logstarttime[FriDec2100:06:40CST2018] UserDao 更新updatelogendtime[FriDec2100:06:40CST2018]

還可以進一步多個 MethodInterceptor 進行過濾篩選

publicclassLogInterceptor2implementsMethodInterceptor{@OverridepublicObjectintercept(Object object, Method method, Object[] objects, MethodProxy methodProxy)throwsThrowable{? ? ? ? before();? ? ? ? Object result = methodProxy.invokeSuper(object, objects);? ? ? ? after();returnresult;? ? }privatevoidbefore(){? ? ? ? System.out.println(String.format("log2 start time [%s] ",newDate()));? ? }privatevoidafter(){? ? ? ? System.out.println(String.format("log2 end time [%s] ",newDate()));? ? }}// 回調過濾器: 在CGLib回調時可以設置對不同方法執(zhí)行不同的回調邏輯,或者根本不執(zhí)行回調。publicclassDaoFilterimplementsCallbackFilter{@Overridepublicintaccept(Method method){if("select".equals(method.getName())) {return0;// Callback 列表第1個攔截器}return1;// Callback 列表第2個攔截器,return 2 則為第3個,以此類推}}

再次測試

publicclassCglibTest2{publicstaticvoidmain(String[] args){? ? ? ? LogInterceptor logInterceptor =newLogInterceptor();? ? ? ? LogInterceptor2 logInterceptor2 =newLogInterceptor2();? ? ? ? Enhancer enhancer =newEnhancer();? ? ? ? enhancer.setSuperclass(UserDao.class);// 設置超類,cglib是通過繼承來實現(xiàn)的enhancer.setCallbacks(newCallback[]{logInterceptor, logInterceptor2, NoOp.INSTANCE});// 設置多個攔截器,NoOp.INSTANCE是一個空攔截器,不做任何處理enhancer.setCallbackFilter(newDaoFilter());? ? ? ? UserDao proxy = (UserDao) enhancer.create();// 創(chuàng)建代理類proxy.select();? ? ? ? proxy.update();? ? }}

運行結果

log start time [Fri Dec2100:22:39CST2018] UserDao 查詢 selectByIdlog end time [Fri Dec2100:22:39CST2018] log2 start time [Fri Dec2100:22:39CST2018] UserDao 更新 updatelog2 end time [Fri Dec2100:22:39CST2018]

CGLIB 創(chuàng)建動態(tài)代理類的模式是:

查找目標類上的所有非final 的public類型的方法定義;

將這些方法的定義轉換成字節(jié)碼;

將組成的字節(jié)碼轉換成相應的代理的class對象;

實現(xiàn) MethodInterceptor接口,用來處理對代理類上所有方法的請求

JDK動態(tài)代理與CGLIB動態(tài)代理對比

JDK動態(tài)代理:基于Java反射機制實現(xiàn),必須要實現(xiàn)了接口的業(yè)務類才能用這種辦法生成代理對象。

cglib動態(tài)代理:基于ASM機制實現(xiàn),通過生成業(yè)務類的子類作為代理類。

JDK Proxy 的優(yōu)勢:

最小化依賴關系,減少依賴意味著簡化開發(fā)和維護,JDK 本身的支持,可能比 cglib 更加可靠。

平滑進行 JDK 版本升級,而字節(jié)碼類庫通常需要進行更新以保證在新版 Java 上能夠使用。

代碼實現(xiàn)簡單。

基于類似 cglib 框架的優(yōu)勢:

無需實現(xiàn)接口,達到代理類無侵入

只操作我們關心的類,而不必為其他相關類增加工作量。

高性能

面試題

來源于網上,用于幫助理解和掌握,歡迎補充

描述動態(tài)代理的幾種實現(xiàn)方式?分別說出相應的優(yōu)缺點

代理可以分為 “靜態(tài)代理” 和 “動態(tài)代理”,動態(tài)代理又分為 “JDK動態(tài)代理” 和 “CGLIB動態(tài)代理” 實現(xiàn)。

靜態(tài)代理:代理對象和實際對象都繼承了同一個接口,在代理對象中指向的是實際對象的實例,這樣對外暴露的是代理對象而真正調用的是 Real Object

優(yōu)點:可以很好的保護實際對象的業(yè)務邏輯對外暴露,從而提高安全性。

缺點:不同的接口要有不同的代理類實現(xiàn),會很冗余

JDK 動態(tài)代理

為了解決靜態(tài)代理中,生成大量的代理類造成的冗余;

JDK 動態(tài)代理只需要實現(xiàn) InvocationHandler 接口,重寫 invoke 方法便可以完成代理的實現(xiàn),

jdk的代理是利用反射生成代理類 Proxyxx.class 代理類字節(jié)碼,并生成對象

jdk動態(tài)代理之所以只能代理接口是因為代理類本身已經extends了Proxy,而java是不允許多重繼承的,但是允許實現(xiàn)多個接口

優(yōu)點:解決了靜態(tài)代理中冗余的代理實現(xiàn)類問題。

缺點:JDK 動態(tài)代理是基于接口設計實現(xiàn)的,如果沒有接口,會拋異常。

CGLIB 代理

由于 JDK 動態(tài)代理限制了只能基于接口設計,而對于沒有接口的情況,JDK方式解決不了;

CGLib 采用了非常底層的字節(jié)碼技術,其原理是通過字節(jié)碼技術為一個類創(chuàng)建子類,并在子類中采用方法攔截的技術攔截所有父類方法的調用,順勢織入橫切邏輯,來完成動態(tài)代理的實現(xiàn)。

實現(xiàn)方式實現(xiàn) MethodInterceptor 接口,重寫 intercept 方法,通過 Enhancer 類的回調方法來實現(xiàn)。

但是CGLib在創(chuàng)建代理對象時所花費的時間卻比JDK多得多,所以對于單例的對象,因為無需頻繁創(chuàng)建對象,用CGLib合適,反之,使用JDK方式要更為合適一些。

同時,由于CGLib由于是采用動態(tài)創(chuàng)建子類的方法,對于final方法,無法進行代理。

優(yōu)點:沒有接口也能實現(xiàn)動態(tài)代理,而且采用字節(jié)碼增強技術,性能也不錯。

缺點:技術實現(xiàn)相對難理解些。

CGlib 對接口實現(xiàn)代理?

importnet.sf.cglib.proxy.Enhancer;importnet.sf.cglib.proxy.MethodInterceptor;importnet.sf.cglib.proxy.MethodProxy;importproxy.UserService;importjava.lang.reflect.Method;/**

* 創(chuàng)建代理類的工廠 該類要實現(xiàn) MethodInterceptor 接口。

* 該類中完成三樣工作:

* (1)聲明目標類的成員變量,并創(chuàng)建以目標類對象為參數(shù)的構造器。用于接收目標對象

* (2)定義代理的生成方法,用于創(chuàng)建代理對象。方法名是任意的。代理對象即目標類的子類

* (3)定義回調接口方法。對目標類的增強這在這里完成

*/publicclassCGLibFactoryimplementsMethodInterceptor{// 聲明目標類的成員變量privateUserService target;publicCGLibFactory(UserService target){this.target = target;? ? }// 定義代理的生成方法,用于創(chuàng)建代理對象publicUserServicemyCGLibCreator(){? ? ? ? Enhancer enhancer =newEnhancer();// 為代理對象設置父類,即指定目標類enhancer.setSuperclass(UserService.class);/**

? ? ? ? * 設置回調接口對象 注意,只所以在setCallback()方法中可以寫上this,

? ? ? ? * 是因為MethodIntecepter接口繼承自Callback,是其子接口

? ? ? ? */enhancer.setCallback(this);return(UserService) enhancer.create();// create用以生成CGLib代理對象}@OverridepublicObjectintercept(Object obj, Method method, Object[] args, MethodProxy proxy)throwsThrowable{? ? ? ? System.out.println("start invoke "+ method.getName());? ? ? ? Object result = method.invoke(target, args);? ? ? ? System.out.println("end invoke "+ method.getName());returnresult;? ? }}

參考:

《Java核心技術》卷1

《深入理解Java虛擬機》7.3

java docs:https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Proxy.html

Java三種代理模式:靜態(tài)代理、動態(tài)代理和cglib代理

描述動態(tài)代理的幾種實現(xiàn)方式 分別說出相應的優(yōu)缺點

JDK動態(tài)代理詳解

Java動態(tài)代理機制詳解(JDK 和CGLIB,Javassist,ASM)

靜態(tài)代理和動態(tài)代理的理解

后記

歡迎評論、轉發(fā)、分享,您的支持是我最大的動力

更多內容可訪問我的個人博客:http://laijianfeng.org

關注【小旋鋒】微信公眾號,及時接收博文推送

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容