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

后端Java編程

創(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é)碼文件

靜態(tài)代理

我們先通過(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)代理的缺點(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ù)斯议。

如何改進(jìn)?

當(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)有的方案活孩。

常見(jiàn)的字節(jié)碼操作類庫(kù)

這里有一些介紹: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)代理

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è)代理類

代理類的調(diào)用過(guò)程

生成的代理類到底長(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)大家另外查閱其他文章或者直接看源碼

CGLIB動(dòng)態(tài)代理

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)

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

Java動(dòng)態(tài)代理機(jī)制詳解(JDK 和CGLIB,Javassist膳沽,ASM)

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

后記

歡迎評(píng)論汗菜、轉(zhuǎn)發(fā)、分享挑社,您的支持是我最大的動(dòng)力

更多內(nèi)容可訪問(wèn)我的個(gè)人博客:http://laijianfeng.org

關(guān)注【小旋鋒】微信公眾號(hào)陨界,及時(shí)接收博文推送

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市痛阻,隨后出現(xiàn)的幾起案子菌瘪,更是在濱河造成了極大的恐慌,老刑警劉巖阱当,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件俏扩,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡弊添,警方通過(guò)查閱死者的電腦和手機(jī)动猬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)表箭,“玉大人,你說(shuō)我怎么就攤上這事∶庾辏” “怎么了彼水?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)极舔。 經(jīng)常有香客問(wèn)我凤覆,道長(zhǎng),這世上最難降的妖魔是什么拆魏? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任盯桦,我火速辦了婚禮,結(jié)果婚禮上渤刃,老公的妹妹穿的比我還像新娘拥峦。我一直安慰自己,他們只是感情好卖子,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布略号。 她就那樣靜靜地躺著,像睡著了一般洋闽。 火紅的嫁衣襯著肌膚如雪玄柠。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,749評(píng)論 1 289
  • 那天诫舅,我揣著相機(jī)與錄音羽利,去河邊找鬼。 笑死刊懈,一個(gè)胖子當(dāng)著我的面吹牛这弧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播俏讹,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼当宴,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了泽疆?” 一聲冷哼從身側(cè)響起户矢,我...
    開(kāi)封第一講書(shū)人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎殉疼,沒(méi)想到半個(gè)月后梯浪,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瓢娜,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年挂洛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片眠砾。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡虏劲,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情柒巫,我是刑警寧澤励堡,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站堡掏,受9級(jí)特大地震影響应结,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜泉唁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一鹅龄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧亭畜,春花似錦扮休、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至宝踪,卻和暖如春侨糟,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背瘩燥。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工秕重, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人厉膀。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓溶耘,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親服鹅。 傳聞我的和親對(duì)象是個(gè)殘疾皇子凳兵,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 1. 代理模式 代理模式是一種比較好的理解的設(shè)計(jì)模式。簡(jiǎn)單來(lái)說(shuō)就是 我們使用代理對(duì)象來(lái)代替對(duì)真實(shí)對(duì)象(real o...
    前端三少爺閱讀 245評(píng)論 0 0
  • 前言 《設(shè)計(jì)模式自習(xí)室》系列企软,顧名思義庐扫,本系列文章帶你溫習(xí)常見(jiàn)的設(shè)計(jì)模式。主要內(nèi)容有: 該模式的介紹仗哨,包括:引子形庭、...
    蠻三刀醬閱讀 436評(píng)論 0 0
  • 一,打破砂鍋問(wèn)到底 什么事代理模式厌漂? 什么是靜態(tài)代理萨醒,有啥缺陷? 什么是動(dòng)態(tài)代理苇倡? JDK動(dòng)態(tài)代理是如何動(dòng)態(tài)生成類...
    JayDroid閱讀 948評(píng)論 0 58
  • 俗話說(shuō):Coder不知?jiǎng)討B(tài)代理,走在路上沒(méi)人理O省8咧啊!所以本文嘗試說(shuō)明白java代理模式辞州,代理中的靜態(tài)代理和動(dòng)態(tài)代理...
    chanyi閱讀 5,325評(píng)論 1 5
  • 久違的晴天,家長(zhǎng)會(huì)寥粹。 家長(zhǎng)大會(huì)開(kāi)好到教室時(shí)变过,離放學(xué)已經(jīng)沒(méi)多少時(shí)間了。班主任說(shuō)已經(jīng)安排了三個(gè)家長(zhǎng)分享經(jīng)驗(yàn)涝涤。 放學(xué)鈴聲...
    飄雪兒5閱讀 7,513評(píng)論 16 22