創(chuàng)建時(shí)間:2018-12-21 01:46
字?jǐn)?shù):5,622閱讀:1706評(píng)論:
動(dòng)態(tài)代理在Java中有著廣泛的應(yīng)用艺蝴,比如Spring AOP池户、Hibernate數(shù)據(jù)查詢距境、測(cè)試框架的后端mock泽篮、RPC遠(yuǎn)程調(diào)用、Java注解對(duì)象獲取、日志、用戶鑒權(quán)吟税、全局性異常處理凹耙、性能監(jiān)控,甚至事務(wù)處理等肠仪。
本文主要介紹Java中兩種常見(jiàn)的動(dòng)態(tài)代理方式:JDK原生動(dòng)態(tài)代理和CGLIB動(dòng)態(tài)代理肖抱。
由于Java動(dòng)態(tài)代理與java反射機(jī)制關(guān)系緊密,請(qǐng)讀者確保已經(jīng)了解了Java反射機(jī)制异旧,可參考上一篇文章《Java反射機(jī)制詳解》
本文將介紹的Java動(dòng)態(tài)代理與設(shè)計(jì)模式中的代理模式有關(guān)意述,什么是代理模式呢?
代理模式:給某一個(gè)對(duì)象提供一個(gè)代理吮蛹,并由代理對(duì)象來(lái)控制對(duì)真實(shí)對(duì)象的訪問(wèn)荤崇。代理模式是一種結(jié)構(gòu)型設(shè)計(jì)模式。
代理模式角色分為 3 種:
Subject(抽象主題角色):定義代理類和真實(shí)主題的公共對(duì)外方法潮针,也是代理類代理真實(shí)主題的方法术荤;
RealSubject(真實(shí)主題角色):真正實(shí)現(xiàn)業(yè)務(wù)邏輯的類;
Proxy(代理主題角色):用來(lái)代理和封裝真實(shí)主題然低;
代理模式的結(jié)構(gòu)比較簡(jiǎn)單喜每,其核心是代理類,為了讓客戶端能夠一致性地對(duì)待真實(shí)對(duì)象和代理對(duì)象雳攘,在代理模式中引入了抽象層
代理模式類圖
代理模式按照職責(zé)(使用場(chǎng)景)來(lái)分類,至少可以分為以下幾類:1枫笛、遠(yuǎn)程代理吨灭。 2、虛擬代理刑巧。 3喧兄、Copy-on-Write 代理。 4啊楚、保護(hù)(Protect or Access)代理吠冤。 5、Cache代理恭理。 6拯辙、防火墻(Firewall)代理。 7颜价、同步化(Synchronization)代理涯保。 8、智能引用(Smart Reference)代理等等周伦。
如果根據(jù)字節(jié)碼的創(chuàng)建時(shí)機(jī)來(lái)分類夕春,可以分為靜態(tài)代理和動(dòng)態(tài)代理:
所謂靜態(tài)也就是在程序運(yùn)行前就已經(jīng)存在代理類的字節(jié)碼文件,代理類和真實(shí)主題角色的關(guān)系在運(yùn)行前就確定了专挪。
而動(dòng)態(tài)代理的源碼是在程序運(yùn)行期間由JVM根據(jù)反射等機(jī)制動(dòng)態(tài)的生成及志,所以在運(yùn)行前并不存在代理類的字節(jié)碼文件
我們先通過(guò)實(shí)例來(lái)學(xué)習(xí)靜態(tài)代理片排,然后理解靜態(tài)代理的缺點(diǎn),再來(lái)學(xué)習(xí)本文的主角:動(dòng)態(tài)代理
編寫(xiě)一個(gè)接口 UserService 速侈,以及該接口的一個(gè)實(shí)現(xiàn)類 UserServiceImpl
publicinterfaceUserService{publicvoidselect();publicvoidupdate();}publicclassUserServiceImplimplementsUserService{publicvoidselect(){? ? ? ? ? System.out.println("查詢 selectById");? ? }publicvoidupdate(){? ? ? ? System.out.println("更新 update");? ? }}
我們將通過(guò)靜態(tài)代理對(duì) UserServiceImpl 進(jìn)行功能增強(qiáng)率寡,在調(diào)用select和update之前記錄一些日志。寫(xiě)一個(gè)代理類 UserServiceProxy锌畸,代理類需要實(shí)現(xiàn) UserService
publicclassUserServiceProxyimplementsUserService{privateUserService target;// 被代理的對(duì)象publicUserServiceProxy(UserService target){this.target = target;? ? }publicvoidselect(){? ? ? ? before();? ? ? ? target.select();// 這里才實(shí)際調(diào)用真實(shí)主題角色的方法after();? ? }publicvoidupdate(){? ? ? ? before();? ? ? ? target.update();// 這里才實(shí)際調(diào)用真實(shí)主題角色的方法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()));? ? }}
客戶端測(cè)試
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]
通過(guò)靜態(tài)代理勇劣,我們達(dá)到了功能增強(qiáng)的目的,而且沒(méi)有侵入原代碼潭枣,這是靜態(tài)代理的一個(gè)優(yōu)點(diǎn)比默。
雖然靜態(tài)代理實(shí)現(xiàn)簡(jiǎn)單,且不侵入原代碼盆犁,但是命咐,當(dāng)場(chǎng)景稍微復(fù)雜一些的時(shí)候,靜態(tài)代理的缺點(diǎn)也會(huì)暴露出來(lái)谐岁。
1醋奠、 當(dāng)需要代理多個(gè)類的時(shí)候,由于代理對(duì)象要實(shí)現(xiàn)與目標(biāo)對(duì)象一致的接口伊佃,有兩種方式:
只維護(hù)一個(gè)代理類窜司,由這個(gè)代理類實(shí)現(xiàn)多個(gè)接口,但是這樣就導(dǎo)致代理類過(guò)于龐大
新建多個(gè)代理類航揉,每個(gè)目標(biāo)對(duì)象對(duì)應(yīng)一個(gè)代理類塞祈,但是這樣會(huì)產(chǎn)生過(guò)多的代理類
2、 當(dāng)接口需要增加帅涂、刪除议薪、修改方法的時(shí)候,目標(biāo)對(duì)象與代理類都要同時(shí)修改媳友,不易維護(hù)斯议。
當(dāng)然是讓代理類動(dòng)態(tài)的生成啦醇锚,也就是動(dòng)態(tài)代理哼御。
為什么類可以動(dòng)態(tài)的生成?
這就涉及到Java虛擬機(jī)的類加載機(jī)制了搂抒,推薦翻看《深入理解Java虛擬機(jī)》7.3節(jié) 類加載的過(guò)程艇搀。
Java虛擬機(jī)類加載過(guò)程主要分為五個(gè)階段:加載、驗(yàn)證求晶、準(zhǔn)備焰雕、解析、初始化芳杏。其中加載階段需要完成以下3件事情:
通過(guò)一個(gè)類的全限定名來(lái)獲取定義此類的二進(jìn)制字節(jié)流
將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)
在內(nèi)存中生成一個(gè)代表這個(gè)類的java.lang.Class對(duì)象矩屁,作為方法區(qū)這個(gè)類的各種數(shù)據(jù)訪問(wèn)入口
由于虛擬機(jī)規(guī)范對(duì)這3點(diǎn)要求并不具體辟宗,所以實(shí)際的實(shí)現(xiàn)是非常靈活的,關(guān)于第1點(diǎn)吝秕,獲取類的二進(jìn)制字節(jié)流(class字節(jié)碼)就有很多途徑:
從ZIP包獲取泊脐,這是JAR、EAR烁峭、WAR等格式的基礎(chǔ)
從網(wǎng)絡(luò)中獲取容客,典型的應(yīng)用是 Applet
運(yùn)行時(shí)計(jì)算生成,這種場(chǎng)景使用最多的是動(dòng)態(tài)代理技術(shù)约郁,在 java.lang.reflect.Proxy 類中缩挑,就是用了 ProxyGenerator.generateProxyClass 來(lái)為特定接口生成形式為*$Proxy的代理類的二進(jìn)制字節(jié)流
由其它文件生成,典型應(yīng)用是JSP鬓梅,即由JSP文件生成對(duì)應(yīng)的Class類
從數(shù)據(jù)庫(kù)中獲取等等
所以供置,動(dòng)態(tài)代理就是想辦法,根據(jù)接口或目標(biāo)對(duì)象绽快,計(jì)算出代理類的字節(jié)碼芥丧,然后再加載到JVM中使用。但是如何計(jì)算坊罢?如何生成续担?情況也許比想象的復(fù)雜得多,我們需要借助現(xiàn)有的方案活孩。
這里有一些介紹:https://java-source.net/open-source/bytecode-libraries
Apache BCEL (Byte Code Engineering Library):是Java classworking廣泛使用的一種框架赤拒,它可以深入到JVM匯編語(yǔ)言進(jìn)行類操作的細(xì)節(jié)。
ObjectWeb ASM:是一個(gè)Java字節(jié)碼操作框架诱鞠。它可以用于直接以二進(jìn)制形式動(dòng)態(tài)生成stub根類或其他代理類,或者在加載時(shí)動(dòng)態(tài)修改類这敬。
CGLIB(Code Generation Library):是一個(gè)功能強(qiáng)大航夺,高性能和高質(zhì)量的代碼生成庫(kù),用于擴(kuò)展JAVA類并在運(yùn)行時(shí)實(shí)現(xiàn)接口崔涂。
Javassist:是Java的加載時(shí)反射系統(tǒng)阳掐,它是一個(gè)用于在Java中編輯字節(jié)碼的類庫(kù); 它使Java程序能夠在運(yùn)行時(shí)定義新類,并在JVM加載之前修改類文件冷蚂。
…
實(shí)現(xiàn)動(dòng)態(tài)代理的思考方向
為了讓生成的代理類與目標(biāo)對(duì)象(真實(shí)主題角色)保持一致性缭保,從現(xiàn)在開(kāi)始將介紹以下兩種最常見(jiàn)的方式:
通過(guò)實(shí)現(xiàn)接口的方式 -> JDK動(dòng)態(tài)代理
通過(guò)繼承類的方式 -> CGLIB動(dòng)態(tài)代理
注:使用ASM對(duì)使用者要求比較高,使用Javassist會(huì)比較麻煩
JDK動(dòng)態(tài)代理主要涉及兩個(gè)類:java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler蝙茶,我們?nèi)匀煌ㄟ^(guò)案例來(lái)學(xué)習(xí)
編寫(xiě)一個(gè)調(diào)用邏輯處理器 LogHandler 類艺骂,提供日志增強(qiáng)功能,并實(shí)現(xiàn) InvocationHandler 接口隆夯;在 LogHandler 中維護(hù)一個(gè)目標(biāo)對(duì)象钳恕,這個(gè)對(duì)象是被代理的對(duì)象(真實(shí)主題角色)别伏;在invoke方法中編寫(xiě)方法調(diào)用的邏輯處理
importjava.lang.reflect.InvocationHandler;importjava.lang.reflect.Method;importjava.util.Date;publicclassLogHandlerimplementsInvocationHandler{? ? Object target;// 被代理的對(duì)象,實(shí)際的方法執(zhí)行者publicLogHandler(Object target){this.target = target;? ? }@OverridepublicObjectinvoke(Object proxy, Method method, Object[] args)throwsThrowable{? ? ? ? before();? ? ? ? Object result = method.invoke(target, args);// 調(diào)用 target 的 method 方法after();returnresult;// 返回方法的執(zhí)行結(jié)果}// 調(diào)用invoke方法之前執(zhí)行privatevoidbefore(){? ? ? ? System.out.println(String.format("log start time [%s] ",newDate()));? ? }// 調(diào)用invoke方法之后執(zhí)行privatevoidafter(){? ? ? ? System.out.println(String.format("log end time [%s] ",newDate()));? ? }}
編寫(xiě)客戶端忧额,獲取動(dòng)態(tài)生成的代理類的對(duì)象須借助 Proxy 類的 newProxyInstance 方法厘肮,具體步驟可見(jiàn)代碼和注釋
importproxy.UserService;importproxy.UserServiceImpl;importjava.lang.reflect.InvocationHandler;importjava.lang.reflect.Proxy;publicclassClient2{publicstaticvoidmain(String[] args)throwsIllegalAccessException, InstantiationException{// 設(shè)置變量可以保存動(dòng)態(tài)代理類,默認(rèn)名稱以 $Proxy0 格式命名// System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");// 1. 創(chuàng)建被代理的對(duì)象睦番,UserService接口的實(shí)現(xiàn)類UserServiceImpl userServiceImpl =newUserServiceImpl();// 2. 獲取對(duì)應(yīng)的 ClassLoaderClassLoader classLoader = userServiceImpl.getClass().getClassLoader();// 3. 獲取所有接口的Class类茂,這里的UserServiceImpl只實(shí)現(xiàn)了一個(gè)接口UserService,Class[] interfaces = userServiceImpl.getClass().getInterfaces();// 4. 創(chuàng)建一個(gè)將傳給代理類的調(diào)用請(qǐng)求處理器托嚣,處理所有的代理對(duì)象上的方法調(diào)用//? ? 這里創(chuàng)建的是一個(gè)自定義的日志處理器巩检,須傳入實(shí)際的執(zhí)行對(duì)象 userServiceImplInvocationHandler logHandler =newLogHandler(userServiceImpl);/*
? ? ? ? ? 5.根據(jù)上面提供的信息,創(chuàng)建代理對(duì)象 在這個(gè)過(guò)程中注益,
? ? ? ? ? ? ? a.JDK會(huì)通過(guò)根據(jù)傳入的參數(shù)信息動(dòng)態(tài)地在內(nèi)存中創(chuàng)建和.class 文件等同的字節(jié)碼
? ? ? ? ? ? ? b.然后根據(jù)相應(yīng)的字節(jié)碼轉(zhuǎn)換成對(duì)應(yīng)的class碴巾,
? ? ? ? ? ? ? c.然后調(diào)用newInstance()創(chuàng)建代理實(shí)例
? ? ? ? */UserService proxy = (UserService) Proxy.newProxyInstance(classLoader, interfaces, logHandler);// 調(diào)用代理的方法proxy.select();? ? ? ? proxy.update();// 保存JDK動(dòng)態(tài)代理生成的代理類,類名保存為 UserServiceProxy// ProxyUtils.generateClassFile(userServiceImpl.getClass(), "UserServiceProxy");}}
運(yùn)行結(jié)果
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)定義了代理對(duì)象調(diào)用方法時(shí)希望執(zhí)行的動(dòng)作丑搔,用于集中處理在動(dòng)態(tài)代理類對(duì)象上的方法調(diào)用
java.lang.reflect.Proxy
static InvocationHandler getInvocationHandler(Object proxy)用于獲取指定代理對(duì)象所關(guān)聯(lián)的調(diào)用處理器
static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)返回指定接口的代理類
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)構(gòu)造實(shí)現(xiàn)指定接口的代理類的一個(gè)新實(shí)例厦瓢,所有方法會(huì)調(diào)用給定處理器對(duì)象的 invoke 方法
static boolean isProxyClass(Class<?> cl)返回 cl 是否為一個(gè)代理類
生成的代理類到底長(zhǎng)什么樣子呢?借助下面的工具類啤月,把代理類保存下來(lái)再探個(gè)究竟
(通過(guò)設(shè)置環(huán)境變量sun.misc.ProxyGenerator.saveGeneratedFiles=true也可以保存代理類)
importsun.misc.ProxyGenerator;importjava.io.FileOutputStream;importjava.io.IOException;publicclassProxyUtils{/**
? ? * 將根據(jù)類信息動(dòng)態(tài)生成的二進(jìn)制字節(jié)碼保存到硬盤(pán)中煮仇,默認(rèn)的是clazz目錄下
? ? * params: clazz 需要生成動(dòng)態(tài)代理類的類
? ? * proxyName: 為動(dòng)態(tài)生成的代理類的名稱
? ? */publicstaticvoidgenerateClassFile(Class clazz, String proxyName){// 根據(jù)類信息和提供的代理類名稱,生成字節(jié)碼byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());? ? ? ? String paths = clazz.getResource(".").getPath();? ? ? ? System.out.println(paths);? ? ? ? FileOutputStream out =null;try{//保留到硬盤(pán)中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 測(cè)試類的main的最后面加入一行代碼
// 保存JDK動(dòng)態(tài)代理生成的代理類谎仲,類名保存為 UserServiceProxyProxyUtils.generateClassFile(userServiceImpl.getClass(),"UserServiceProxy");
IDEA 再次運(yùn)行之后就可以在 target 的類路徑下找到 UserServiceProxy.class浙垫,雙擊后IDEA的反編譯插件會(huì)將該二進(jìn)制class文件
JDK 動(dòng)態(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 類,并且實(shí)現(xiàn)了被代理的所有接口郑诺,以及equals夹姥、hashCode、toString等方法
由于 UserServiceProxy 繼承了 Proxy 類辙诞,所以每個(gè)代理類都會(huì)關(guān)聯(lián)一個(gè) InvocationHandler 方法調(diào)用處理器
類和所有方法都被public final修飾辙售,所以代理類只可被使用,不可以再被繼承
每個(gè)方法都有一個(gè) Method 對(duì)象來(lái)描述飞涂,Method 對(duì)象在static靜態(tài)代碼塊中創(chuàng)建旦部,以m + 數(shù)字的格式命名
調(diào)用方法的時(shí)候通過(guò)super.h.invoke(this, m1, (Object[])null);調(diào)用,其中的super.h.invoke實(shí)際上是在創(chuàng)建代理的時(shí)候傳遞給Proxy.newProxyInstance的 LogHandler 對(duì)象较店,它繼承 InvocationHandler 類士八,負(fù)責(zé)實(shí)際的調(diào)用處理邏輯
而 LogHandler 的 invoke 方法接收到 method、args 等參數(shù)后梁呈,進(jìn)行一些處理婚度,然后通過(guò)反射讓被代理的對(duì)象 target 執(zhí)行方法
@OverridepublicObjectinvoke(Object proxy, Method method, Object[] args)throwsThrowable{? ? ? ? before();? ? ? ? Object result = method.invoke(target, args);// 調(diào)用 target 的 method 方法after();returnresult;// 返回方法的執(zhí)行結(jié)果}
JDK動(dòng)態(tài)代理執(zhí)行方法調(diào)用的過(guò)程簡(jiǎn)圖如下:
JDK動(dòng)態(tài)代理執(zhí)行方法調(diào)用過(guò)程
代理類的調(diào)用過(guò)程相信大家都明了了,而關(guān)于Proxy的源碼解析捧杉,還請(qǐng)大家另外查閱其他文章或者直接看源碼
maven引入CGLIB包陕见,然后編寫(xiě)一個(gè)UserDao類秘血,它沒(méi)有接口,只有兩個(gè)方法评甜,select() 和 update()
publicclassUserDao{publicvoidselect(){? ? ? ? System.out.println("UserDao 查詢 selectById");? ? }publicvoidupdate(){? ? ? ? System.out.println("UserDao 更新 update");? ? }}
編寫(xiě)一個(gè) LogInterceptor 灰粮,繼承了 MethodInterceptor,用于方法的攔截回調(diào)
importjava.lang.reflect.Method;importjava.util.Date;publicclassLogInterceptorimplementsMethodInterceptor{/**? ? *@paramobject 表示要進(jìn)行增強(qiáng)的對(duì)象? ? *@parammethod 表示攔截的方法? ? *@paramobjects 數(shù)組表示參數(shù)列表忍坷,基本數(shù)據(jù)類型需要傳入其包裝類型粘舟,如int-->Integer、long-Long佩研、double-->Double? ? *@parammethodProxy 表示對(duì)方法的代理柑肴,invokeSuper方法表示對(duì)被代理對(duì)象方法的調(diào)用? ? *@return執(zhí)行結(jié)果? ? *@throwsThrowable? ? */@OverridepublicObjectintercept(Object object, Method method, Object[] objects, MethodProxy methodProxy)throwsThrowable{? ? ? ? before();? ? ? ? Object result = methodProxy.invokeSuper(object, objects);// 注意這里是調(diào)用 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()));? ? }}
測(cè)試
importnet.sf.cglib.proxy.Enhancer;publicclassCglibTest{publicstaticvoidmain(String[] args){? ? ? ? DaoProxy daoProxy =newDaoProxy();? ? ? ? Enhancer enhancer =newEnhancer();? ? ? ? enhancer.setSuperclass(Dao.class);// 設(shè)置超類,cglib是通過(guò)繼承來(lái)實(shí)現(xiàn)的enhancer.setCallback(daoProxy);? ? ? ? Dao dao = (Dao)enhancer.create();// 創(chuàng)建代理類dao.update();? ? ? ? dao.select();? ? }}
運(yùn)行結(jié)果
logstarttime[FriDec2100:06:40CST2018] UserDao 查詢 selectByIdlogendtime[FriDec2100:06:40CST2018]logstarttime[FriDec2100:06:40CST2018] UserDao 更新updatelogendtime[FriDec2100:06:40CST2018]
還可以進(jìn)一步多個(gè) MethodInterceptor 進(jìn)行過(guò)濾篩選
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()));? ? }}// 回調(diào)過(guò)濾器: 在CGLib回調(diào)時(shí)可以設(shè)置對(duì)不同方法執(zhí)行不同的回調(diào)邏輯绊序,或者根本不執(zhí)行回調(diào)硕舆。publicclassDaoFilterimplementsCallbackFilter{@Overridepublicintaccept(Method method){if("select".equals(method.getName())) {return0;// Callback 列表第1個(gè)攔截器}return1;// Callback 列表第2個(gè)攔截器,return 2 則為第3個(gè)骤公,以此類推}}
再次測(cè)試
publicclassCglibTest2{publicstaticvoidmain(String[] args){? ? ? ? LogInterceptor logInterceptor =newLogInterceptor();? ? ? ? LogInterceptor2 logInterceptor2 =newLogInterceptor2();? ? ? ? Enhancer enhancer =newEnhancer();? ? ? ? enhancer.setSuperclass(UserDao.class);// 設(shè)置超類抚官,cglib是通過(guò)繼承來(lái)實(shí)現(xiàn)的enhancer.setCallbacks(newCallback[]{logInterceptor, logInterceptor2, NoOp.INSTANCE});// 設(shè)置多個(gè)攔截器,NoOp.INSTANCE是一個(gè)空攔截器阶捆,不做任何處理enhancer.setCallbackFilter(newDaoFilter());? ? ? ? UserDao proxy = (UserDao) enhancer.create();// 創(chuàng)建代理類proxy.select();? ? ? ? proxy.update();? ? }}
運(yùn)行結(jié)果
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)建動(dòng)態(tài)代理類的模式是:
查找目標(biāo)類上的所有非final 的public類型的方法定義凌节;
將這些方法的定義轉(zhuǎn)換成字節(jié)碼;
將組成的字節(jié)碼轉(zhuǎn)換成相應(yīng)的代理的class對(duì)象洒试;
實(shí)現(xiàn) MethodInterceptor接口倍奢,用來(lái)處理對(duì)代理類上所有方法的請(qǐng)求
JDK動(dòng)態(tài)代理與CGLIB動(dòng)態(tài)代理對(duì)比
JDK動(dòng)態(tài)代理:基于Java反射機(jī)制實(shí)現(xiàn),必須要實(shí)現(xiàn)了接口的業(yè)務(wù)類才能用這種辦法生成代理對(duì)象垒棋。
cglib動(dòng)態(tài)代理:基于ASM機(jī)制實(shí)現(xiàn)娱挨,通過(guò)生成業(yè)務(wù)類的子類作為代理類。
JDK Proxy 的優(yōu)勢(shì):
最小化依賴關(guān)系捕犬,減少依賴意味著簡(jiǎn)化開(kāi)發(fā)和維護(hù),JDK 本身的支持酵镜,可能比 cglib 更加可靠碉碉。
平滑進(jìn)行 JDK 版本升級(jí),而字節(jié)碼類庫(kù)通常需要進(jìn)行更新以保證在新版 Java 上能夠使用淮韭。
代碼實(shí)現(xiàn)簡(jiǎn)單垢粮。
基于類似 cglib 框架的優(yōu)勢(shì):
無(wú)需實(shí)現(xiàn)接口,達(dá)到代理類無(wú)侵入
只操作我們關(guān)心的類靠粪,而不必為其他相關(guān)類增加工作量蜡吧。
高性能
來(lái)源于網(wǎng)上毫蚓,用于幫助理解和掌握,歡迎補(bǔ)充
描述動(dòng)態(tài)代理的幾種實(shí)現(xiàn)方式昔善?分別說(shuō)出相應(yīng)的優(yōu)缺點(diǎn)
代理可以分為 “靜態(tài)代理” 和 “動(dòng)態(tài)代理”元潘,動(dòng)態(tài)代理又分為 “JDK動(dòng)態(tài)代理” 和 “CGLIB動(dòng)態(tài)代理” 實(shí)現(xiàn)。
靜態(tài)代理:代理對(duì)象和實(shí)際對(duì)象都繼承了同一個(gè)接口君仆,在代理對(duì)象中指向的是實(shí)際對(duì)象的實(shí)例翩概,這樣對(duì)外暴露的是代理對(duì)象而真正調(diào)用的是 Real Object
優(yōu)點(diǎn):可以很好的保護(hù)實(shí)際對(duì)象的業(yè)務(wù)邏輯對(duì)外暴露,從而提高安全性返咱。
缺點(diǎn):不同的接口要有不同的代理類實(shí)現(xiàn)钥庇,會(huì)很冗余
JDK 動(dòng)態(tài)代理:
為了解決靜態(tài)代理中,生成大量的代理類造成的冗余咖摹;
JDK 動(dòng)態(tài)代理只需要實(shí)現(xiàn) InvocationHandler 接口评姨,重寫(xiě) invoke 方法便可以完成代理的實(shí)現(xiàn),
jdk的代理是利用反射生成代理類 Proxyxx.class 代理類字節(jié)碼萤晴,并生成對(duì)象
jdk動(dòng)態(tài)代理之所以只能代理接口是因?yàn)?b>代理類本身已經(jīng)extends了Proxy吐句,而java是不允許多重繼承的,但是允許實(shí)現(xiàn)多個(gè)接口
優(yōu)點(diǎn):解決了靜態(tài)代理中冗余的代理實(shí)現(xiàn)類問(wèn)題硫眯。
缺點(diǎn):JDK 動(dòng)態(tài)代理是基于接口設(shè)計(jì)實(shí)現(xiàn)的蕴侧,如果沒(méi)有接口,會(huì)拋異常两入。
CGLIB 代理:
由于 JDK 動(dòng)態(tài)代理限制了只能基于接口設(shè)計(jì)净宵,而對(duì)于沒(méi)有接口的情況,JDK方式解決不了裹纳;
CGLib 采用了非常底層的字節(jié)碼技術(shù)择葡,其原理是通過(guò)字節(jié)碼技術(shù)為一個(gè)類創(chuàng)建子類,并在子類中采用方法攔截的技術(shù)攔截所有父類方法的調(diào)用剃氧,順勢(shì)織入橫切邏輯敏储,來(lái)完成動(dòng)態(tài)代理的實(shí)現(xiàn)。
實(shí)現(xiàn)方式實(shí)現(xiàn) MethodInterceptor 接口朋鞍,重寫(xiě) intercept 方法已添,通過(guò) Enhancer 類的回調(diào)方法來(lái)實(shí)現(xiàn)。
但是CGLib在創(chuàng)建代理對(duì)象時(shí)所花費(fèi)的時(shí)間卻比JDK多得多滥酥,所以對(duì)于單例的對(duì)象更舞,因?yàn)闊o(wú)需頻繁創(chuàng)建對(duì)象,用CGLib合適坎吻,反之缆蝉,使用JDK方式要更為合適一些。
同時(shí),由于CGLib由于是采用動(dòng)態(tài)創(chuàng)建子類的方法刊头,對(duì)于final方法黍瞧,無(wú)法進(jìn)行代理。
優(yōu)點(diǎn):沒(méi)有接口也能實(shí)現(xiàn)動(dòng)態(tài)代理原杂,而且采用字節(jié)碼增強(qiáng)技術(shù)印颤,性能也不錯(cuò)。
缺點(diǎn):技術(shù)實(shí)現(xiàn)相對(duì)難理解些污尉。
CGlib 對(duì)接口實(shí)現(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)建代理類的工廠 該類要實(shí)現(xiàn) MethodInterceptor 接口。
* 該類中完成三樣工作:
* (1)聲明目標(biāo)類的成員變量,并創(chuàng)建以目標(biāo)類對(duì)象為參數(shù)的構(gòu)造器。用于接收目標(biāo)對(duì)象
* (2)定義代理的生成方法悼做,用于創(chuàng)建代理對(duì)象。方法名是任意的兴喂。代理對(duì)象即目標(biāo)類的子類
* (3)定義回調(diào)接口方法。對(duì)目標(biāo)類的增強(qiáng)這在這里完成
*/publicclassCGLibFactoryimplementsMethodInterceptor{// 聲明目標(biāo)類的成員變量privateUserService target;publicCGLibFactory(UserService target){this.target = target;? ? }// 定義代理的生成方法,用于創(chuàng)建代理對(duì)象publicUserServicemyCGLibCreator(){? ? ? ? Enhancer enhancer =newEnhancer();// 為代理對(duì)象設(shè)置父類焚志,即指定目標(biāo)類enhancer.setSuperclass(UserService.class);/**
? ? ? ? * 設(shè)置回調(diào)接口對(duì)象 注意衣迷,只所以在setCallback()方法中可以寫(xiě)上this,
? ? ? ? * 是因?yàn)镸ethodIntecepter接口繼承自Callback酱酬,是其子接口
? ? ? ? */enhancer.setCallback(this);return(UserService) enhancer.create();// create用以生成CGLib代理對(duì)象}@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核心技術(shù)》卷1
《深入理解Java虛擬機(jī)》7.3
java docs:https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Proxy.html
Java三種代理模式:靜態(tài)代理壶谒、動(dòng)態(tài)代理和cglib代理
描述動(dòng)態(tài)代理的幾種實(shí)現(xiàn)方式 分別說(shuō)出相應(yīng)的優(yōu)缺點(diǎn)
Java動(dòng)態(tài)代理機(jī)制詳解(JDK 和CGLIB,Javassist膳沽,ASM)
歡迎評(píng)論汗菜、轉(zhuǎn)發(fā)、分享挑社,您的支持是我最大的動(dòng)力
更多內(nèi)容可訪問(wèn)我的個(gè)人博客:http://laijianfeng.org
關(guān)注【小旋鋒】微信公眾號(hào)陨界,及時(shí)接收博文推送