Java設(shè)計模式之動態(tài)代理
本文主要介紹Java中兩種常見的動態(tài)代理方式:JDK原生動態(tài)代理和CGLIB動態(tài)代理。
什么是代理模式
就是為其他對象提供一種代理以控制對這個對象的訪問。代理可以在不改動目標(biāo)對象的基礎(chǔ)上,增加其他額外的功能(擴(kuò)展功能)。
代理模式角色分為 3 種:
Subject(抽象主題角色):定義代理類和真實(shí)主題的公共對外方法,也是代理類代理真實(shí)主題的方法;RealSubject(真實(shí)主題角色):真正實(shí)現(xiàn)業(yè)務(wù)邏輯的類;Proxy(代理主題角色):用來代理和封裝真實(shí)主題;
如果根據(jù)字節(jié)碼的創(chuàng)建時機(jī)來分類,可以分為靜態(tài)代理和動態(tài)代理:
所謂靜態(tài)也就是在程序運(yùn)行前就已經(jīng)存在代理類的字節(jié)碼文件,代理類和真實(shí)主題角色的關(guān)系在運(yùn)行前就確定了。
而動態(tài)代理的源碼是在程序運(yùn)行期間由JVM根據(jù)反射等機(jī)制動態(tài)的生成,所以在運(yùn)行前并不存在代理類的字節(jié)碼文件
靜態(tài)代理
學(xué)習(xí)動態(tài)代理前,有必要來學(xué)習(xí)一下靜態(tài)代理。
靜態(tài)代理在使用時,需要定義接口或者父類,被代理對象(目標(biāo)對象)與代理對象(Proxy)一起實(shí)現(xiàn)相同的接口或者是繼承相同父類。
來看一個例子,模擬小貓走路的時間。
//?接口
public?interface?Walkable?{
????void?walk();
}
//?實(shí)現(xiàn)類
public?class?Cat?implements?Walkable?{
????@Override
????public?void?walk()?{
????????System.out.println("cat?is?walking...");
????????try?{
????????????Thread.sleep(new?Random().nextInt(1000));
????????}?catch?(InterruptedException?e)?{
????????????e.printStackTrace();
????????}
????}
}
如果我想知道走路的時間怎么辦?可以將實(shí)現(xiàn)類Cat修改為:
public?class?Cat?implements?Walkable?{
????@Override
????public?void?walk()?{
????????long?start?=?System.currentTimeMillis();
????????System.out.println("cat?is?walking...");
????????try?{
????????????Thread.sleep(new?Random().nextInt(1000));
????????}?catch?(InterruptedException?e)?{
????????????e.printStackTrace();
????????}
????????long?end?=?System.currentTimeMillis();
????????System.out.println("walk?time?=?"?+?(end?-?start));
????}
}
這里已經(jīng)侵入了源代碼,如果源代碼是不能改動的,這樣寫顯然是不行的,這里可以引入時間代理類CatTimeProxy。
public?class?CatTimeProxy?implements?Walkable?{
????private?Walkable?walkable;
????public?CatTimeProxy(Walkable?walkable)?{
????????this.walkable?=?walkable;
????}
????@Override
????public?void?walk()?{
????????long?start?=?System.currentTimeMillis();
????????walkable.walk();
????????long?end?=?System.currentTimeMillis();
????????System.out.println("Walk?time?=?"?+?(end?-?start));
????}
}
如果這時候還要加上常見的日志功能,我們還需要創(chuàng)建一個日志代理類CatLogProxy。
public?class?CatLogProxy?implements?Walkable?{
????private?Walkable?walkable;
????public?CatLogProxy(Walkable?walkable)?{
????????this.walkable?=?walkable;
????}
????@Override
????public?void?walk()?{
????????System.out.println("Cat?walk?start...");
????????walkable.walk();
????????System.out.println("Cat?walk?end...");
????}
}
如果我們需要先記錄日志,再獲取行走時間,可以在調(diào)用的地方這么做:
public?static?void?main(String[]?args)?{
????Cat?cat?=?new?Cat();
????CatLogProxy?p1?=?new?CatLogProxy(cat);
????CatTimeProxy?p2?=?new?CatTimeProxy(p1);
????p2.walk();
}
這樣的話,計時是包括打日志的時間的。
靜態(tài)代理的問題
如果我們需要計算SDK中100個方法的運(yùn)行時間,同樣的代碼至少需要重復(fù)100次,并且創(chuàng)建至少100個代理類。往小了說,如果Cat類有多個方法,我們需要知道其他方法的運(yùn)行時間,同樣的代碼也至少需要重復(fù)多次。因此,靜態(tài)代理至少有以下兩個局限性問題:
如果同時代理多個類,依然會導(dǎo)致類無限制擴(kuò)展
如果類中有多個方法,同樣的邏輯需要反復(fù)實(shí)現(xiàn)
所以,我們需要一個通用的代理類來代理所有的類的所有方法,這就需要用到動態(tài)代理技術(shù)。
動態(tài)代理
學(xué)習(xí)任何一門技術(shù),一定要問一問自己,這到底有什么用。其實(shí),在這篇文章的講解過程中,我們已經(jīng)說出了它的主要用途。你發(fā)現(xiàn)沒,使用動態(tài)代理我們居然可以在不改變源碼的情況下,直接在方法中插入自定義邏輯。這有點(diǎn)不太符合我們的一條線走到底的編程邏輯,這種編程模型有一個專業(yè)名稱叫AOP。所謂的AOP,就像刀一樣,抓住時機(jī),趁機(jī)插入。

Jdk動態(tài)代理
JDK實(shí)現(xiàn)代理只需要使用newProxyInstance方法,但是該方法需要接收三個參數(shù):
@CallerSensitive
????public?static?Object?newProxyInstance(ClassLoader?loader,
??????????????????????????????????????????Class>[]?interfaces,
??????????????????????????????????????????InvocationHandler?h)
????????throws?IllegalArgumentException
????{
????????Objects.requireNonNull(h);
????????final?Class>[]?intfs?=?interfaces.clone();
????????final?SecurityManager?sm?=?System.getSecurityManager();
????????if?(sm?!=?null)?{
????????????checkProxyAccess(Reflection.getCallerClass(),?loader,?intfs);
????????}
????????/*
?????????*?Look?up?or?generate?the?designated?proxy?class.
?????????*/
????????Class>?cl?=?getProxyClass0(loader,?intfs);
????????/*
?????????*?Invoke?its?constructor?with?the?designated?invocation?handler.
?????????*/
????????try?{
????????????if?(sm?!=?null)?{
????????????????checkNewProxyPermission(Reflection.getCallerClass(),?cl);
????????????}
????????????final?Constructor>?cons?=?cl.getConstructor(constructorParams);
????????????final?InvocationHandler?ih?=?h;
????????????if?(!Modifier.isPublic(cl.getModifiers()))?{
????????????????AccessController.doPrivileged(new?PrivilegedAction()?{
????????????????????public?Void?run()?{
????????????????????????cons.setAccessible(true);
????????????????????????return?null;
????????????????????}
????????????????});
????????????}
????????????return?cons.newInstance(new?Object[]{h});
????????}?catch?(IllegalAccessException|InstantiationException?e)?{
????????????throw?new?InternalError(e.toString(),?e);
????????}?catch?(InvocationTargetException?e)?{
????????????Throwable?t?=?e.getCause();
????????????if?(t?instanceof?RuntimeException)?{
????????????????throw?(RuntimeException)?t;
????????????}?else?{
????????????????throw?new?InternalError(t.toString(),?t);
????????????}
????????}?catch?(NoSuchMethodException?e)?{
????????????throw?new?InternalError(e.toString(),?e);
????????}
????}
方法是在Proxy類中是靜態(tài)方法,且接收的三個參數(shù)依次為:
ClassLoader loader?//指定當(dāng)前目標(biāo)對象使用類加載器Class[] interfaces?//目標(biāo)對象實(shí)現(xiàn)的接口的類型,使用泛型方式確認(rèn)類型InvocationHandler h?//事件處理器
主要是完成InvocationHandler h的編寫工作。
接口類UserService:
public?interface?UserService?{
????public?void?select();
????public?void?update();
}
接口實(shí)現(xiàn)類,即要代理的類UserServiceImpl:
public?class?UserServiceImpl?implements?UserService?{
????@Override
????public?void?select()?{
????????System.out.println("查詢?selectById");
????}
????@Override
????public?void?update()?{
????????System.out.println("更新?update");
????}
}
代理類UserServiceProxy:
public?class?UserServiceProxy?implements?UserService?{
????private?UserService?target;
????public?UserServiceProxy(UserService?target){
????????this.target?=?target;
????}
????@Override
????public?void?select()?{
????????before();
????????target.select();
????????after();
????}
????@Override
????public?void?update()?{
????????before();
????????target.update();
????????after();
????}
????private?void?before()?{?????//?在執(zhí)行方法之前執(zhí)行
????????System.out.println(String.format("log?start?time?[%s]?",?new?Date()));
????}
????private?void?after()?{??????//?在執(zhí)行方法之后執(zhí)行
????????System.out.println(String.format("log?end?time?[%s]?",?new?Date()));
????}
}
主程序類:
public?class?UserServiceProxyJDKMain?{
????public?static?void?main(String[]?args)?{
????????//?1.?創(chuàng)建被代理的對象,即UserService的實(shí)現(xiàn)類
????????UserServiceImpl?userServiceImpl?=?new?UserServiceImpl();
????????//?2.?獲取對應(yīng)的classLoader
????????ClassLoader?classLoader?=?userServiceImpl.getClass().getClassLoader();
????????//?3.?獲取所有接口的Class,?這里的userServiceImpl只實(shí)現(xiàn)了一個接口UserService,
????????Class[]?interfaces?=?userServiceImpl.getClass().getInterfaces();
????????//?4.?創(chuàng)建一個將傳給代理類的調(diào)用請求處理器,處理所有的代理對象上的方法調(diào)用
????????//?????這里創(chuàng)建的是一個自定義的日志處理器,須傳入實(shí)際的執(zhí)行對象?userServiceImpl
????????InvocationHandler?logHandler?=?new?LogHandler(userServiceImpl);
????????/*
???????????5.根據(jù)上面提供的信息,創(chuàng)建代理對象?在這個過程中,
???????????????a.JDK會通過根據(jù)傳入的參數(shù)信息動態(tài)地在內(nèi)存中創(chuàng)建和.class?文件等同的字節(jié)碼
???????????????b.然后根據(jù)相應(yīng)的字節(jié)碼轉(zhuǎn)換成對應(yīng)的class,
???????????????c.然后調(diào)用newInstance()創(chuàng)建代理實(shí)例
?????????*/
????????//?會動態(tài)生成UserServiceProxy代理類,并且用代理對象實(shí)例化LogHandler,調(diào)用代理對象的.invoke()方法即可
????????UserService?proxy?=?(UserService)?Proxy.newProxyInstance(classLoader,?interfaces,?logHandler);
????????//?調(diào)用代理的方法
????????proxy.select();
????????proxy.update();
????????//?生成class文件的名稱
????????ProxyUtils.generateClassFile(userServiceImpl.getClass(),?"UserServiceJDKProxy");
????}
}
這里可以保存下來代理生成的實(shí)現(xiàn)了接口的代理對象:
public?class?ProxyUtils?{
????/*
?????*?將根據(jù)類信息?動態(tài)生成的二進(jìn)制字節(jié)碼保存到硬盤中,
?????*?默認(rèn)的是clazz目錄下
?????*?params?:clazz?需要生成動態(tài)代理類的類
?????*?proxyName?:?為動態(tài)生成的代理類的名稱
?????*/
????public?static?void?generateClassFile(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?{
????????????//保留到硬盤中
????????????out?=?new?FileOutputStream(paths?+?proxyName?+?".class");
????????????out.write(classFile);
????????????out.flush();
????????}?catch?(Exception?e)?{
????????????e.printStackTrace();
????????}?finally?{
????????????try?{
????????????????out.close();
????????????}?catch?(IOException?e)?{
????????????????e.printStackTrace();
????????????}
????????}
????}
}
動態(tài)代理實(shí)現(xiàn)過程
通過
getProxyClass0()生成代理類。JDK生成的最終真正的代理類,它繼承自Proxy并實(shí)現(xiàn)了我們定義的接口.通過
Proxy.newProxyInstance()生成代理類的實(shí)例對象,創(chuàng)建對象時傳入InvocationHandler類型的實(shí)例。調(diào)用新實(shí)例的方法,即原
InvocationHandler類中的invoke()方法。
代理對象不需要實(shí)現(xiàn)接口,但是目標(biāo)對象一定要實(shí)現(xiàn)接口,否則不能用動態(tài)代理
Cglib動態(tài)代理
JDK的動態(tài)代理機(jī)制只能代理實(shí)現(xiàn)了接口的類,而不能實(shí)現(xiàn)接口的類就不能實(shí)現(xiàn)JDK的動態(tài)代理,cglib是針對類來實(shí)現(xiàn)代理的,他的原理是對指定的目標(biāo)類生成一個子類,并覆蓋其中方法實(shí)現(xiàn)增強(qiáng),但因?yàn)椴捎玫氖抢^承,所以不能對final修飾的類進(jìn)行代理。
Cglib代理,也叫作子類代理,它是在內(nèi)存中構(gòu)建一個子類對象從而實(shí)現(xiàn)對目標(biāo)對象功能的擴(kuò)展。
Cglib子類代理實(shí)現(xiàn)方法:
需要引入
cglib的jar文件,但是Spring的核心包中已經(jīng)包括了Cglib功能,所以直接引入Spring-core.jar即可.引入功能包后,就可以在內(nèi)存中動態(tài)構(gòu)建子類
代理的類不能為
final,否則報錯目標(biāo)對象的方法如果為
final/static,那么就不會被攔截,即不會執(zhí)行目標(biāo)對象額外的業(yè)務(wù)方法.
基本使用
<dependency>
????<groupId>cglibgroupId>
????<artifactId>cglibartifactId>
????<version>2.2version>
dependency>
方法攔截器
public?class?LogInterceptor?implements?MethodInterceptor{
????/*
?????*?@param?o?要進(jìn)行增強(qiáng)的對象
?????*?@param?method?要攔截的方法
?????*?@param?objects?參數(shù)列表,基本數(shù)據(jù)類型需要傳入其包裝類
?????*?@param?methodProxy?對方法的代理,
?????*?@return?執(zhí)行結(jié)果
?????*?@throws?Throwable
?????*/
????@Override
????public?Object?intercept(Object?o,?Method?method,?Object[]?objects,?MethodProxy?methodProxy)?throws?Throwable?{
????????before();
????????Object?result?=?methodProxy.invokeSuper(o,?objects);
????????after();
????????return?result;
????}
????private?void?before()?{
????????System.out.println(String.format("log?start?time?[%s]?",?new?Date()));
????}
????private?void?after()?{
????????System.out.println(String.format("log?end?time?[%s]?",?new?Date()));
????}
}
測試用例
這里保存了代理類的.class文件
public?class?CglibMain?{
????public?static?void?main(String[]?args)?{
????????//?創(chuàng)建Enhancer對象,類似于JDK動態(tài)代理的Proxy類
????????Enhancer?enhancer?=?new?Enhancer();
????????//?設(shè)置目標(biāo)類的字節(jié)碼文件
????????enhancer.setSuperclass(UserDao.class);
????????//?設(shè)置回調(diào)函數(shù)
????????enhancer.setCallback(new?LogInterceptor());
????????//?create會創(chuàng)建代理類
????????UserDao?userDao?=?(UserDao)enhancer.create();
????????userDao.update();
????????userDao.select();
????}
}
結(jié)果
log?start?time?[Mon?Nov?30?17:26:39?CST?2020]?
UserDao?更新?update
log?end?time?[Mon?Nov?30?17:26:39?CST?2020]?
log?start?time?[Mon?Nov?30?17:26:39?CST?2020]?
UserDao?查詢?selectById
log?end?time?[Mon?Nov?30?17:26:39?CST?2020]?
兩種動態(tài)代理對比
JDK 動態(tài)代理
為了解決靜態(tài)代理中,生成大量的代理類造成的冗余;
JDK動態(tài)代理只需要實(shí)現(xiàn)InvocationHandler接口,重寫invoke方法便可以完成代理的實(shí)現(xiàn),jdk的代理是利用反射生成代理類
Proxyxx.class代理類字節(jié)碼,并生成對象jdk動態(tài)代理之所以只能代理接口是因?yàn)榇眍惐旧硪呀?jīng)
extends了Proxy,而java是不允許多重繼承的,但是允許實(shí)現(xiàn)多個接口
優(yōu)點(diǎn):解決了靜態(tài)代理中冗余的代理實(shí)現(xiàn)類問題。
缺點(diǎn):JDK 動態(tài)代理是基于接口設(shè)計實(shí)現(xiàn)的,如果沒有接口,會拋異常。
CGLIB 代理
由于
JDK動態(tài)代理限制了只能基于接口設(shè)計,而對于沒有接口的情況,JDK方式解決不了;CGLib采用了非常底層的字節(jié)碼技術(shù),其原理是通過字節(jié)碼技術(shù)為一個類創(chuàng)建子類,并在子類中采用方法攔截的技術(shù)攔截所有父類方法的調(diào)用,順勢織入橫切邏輯,來完成動態(tài)代理的實(shí)現(xiàn)。實(shí)現(xiàn)方式實(shí)現(xiàn)
MethodInterceptor接口,重寫intercept方法,通過Enhancer類的回調(diào)方法來實(shí)現(xiàn)。但是
CGLib在創(chuàng)建代理對象時所花費(fèi)的時間卻比JDK多得多,所以對于單例的對象,因?yàn)闊o需頻繁創(chuàng)建對象,用CGLib合適,反之,使用JDK方式要更為合適一些。同時,由于
CGLib由于是采用動態(tài)創(chuàng)建子類的方法,對于final方法,無法進(jìn)行代理。
優(yōu)點(diǎn):沒有接口也能實(shí)現(xiàn)動態(tài)代理,而且采用字節(jié)碼增強(qiáng)技術(shù),性能也不錯。
缺點(diǎn):技術(shù)實(shí)現(xiàn)相對難理解些。
