Java動(dòng)態(tài)代理(設(shè)計(jì)模式)代碼詳解
基礎(chǔ):需要具備面向?qū)ο笤O(shè)計(jì)思想,多態(tài)的思想,反射的思想;
Java動(dòng)態(tài)代理機(jī)制的出現(xiàn),使得Java開發(fā)人員不用手工編寫代理類,只要簡(jiǎn)單地指定一組接口及委托類對(duì)象,便能動(dòng)態(tài)地獲得代理類。代理類會(huì)負(fù)責(zé)將所有的方法調(diào)用分派到委托對(duì)象上反射執(zhí)行,在分派執(zhí)行的過程中,開發(fā)人員還可以按需調(diào)整委托類對(duì)象及其功能,這是一套非常靈活有彈性的代理框架。通過閱讀本文,讀者將會(huì)對(duì)Java動(dòng)態(tài)代理機(jī)制有更加深入的理解。本文首先從Java動(dòng)態(tài)代理的運(yùn)行機(jī)制和特點(diǎn)出發(fā),對(duì)其代碼進(jìn)行了分析,推演了動(dòng)態(tài)生成類的內(nèi)部實(shí)現(xiàn)。
代理模式的基本概念和分類
代理模式:為其他對(duì)象提供一個(gè)代理,來控制對(duì)這個(gè)對(duì)象的訪問。代理對(duì)象起到中介作用,可以去掉服務(wù)或者增加額外的服務(wù),或者引用別人的話:“代理類負(fù)責(zé)為委托類預(yù)處理消息,過濾消息并轉(zhuǎn)發(fā)消息,以及進(jìn)行消息被委托類執(zhí)行后的后續(xù)處理?!?/p>
代理模式在開發(fā)中的應(yīng)用場(chǎng)景
遠(yuǎn)程代理:為不同地理的對(duì)象提供局域網(wǎng)代表對(duì)象。
虛擬代理:根據(jù)需要將資源消耗很大的對(duì)象進(jìn)行延遲,真正需要的時(shí)候進(jìn)行創(chuàng)建。比如網(wǎng)頁(yè)中的先顯示文字再顯示圖片。
保護(hù)代理:控制不同用戶的訪問權(quán)限。比如:只有當(dāng)客戶注冊(cè)成功之后,才可以進(jìn)行增刪改查等操作。
智能引用代理:提供對(duì)目標(biāo)代理額外的服務(wù)。
代理模式的實(shí)現(xiàn)方式
使用繼承和聚合實(shí)現(xiàn)動(dòng)態(tài)代理,哪種更好呢!
public interface Moveable { public void move(); } public class Car implements Moveable{ @Override public void move() { try { Thread.sleep(new Random().nextint(1000)); System.out.println("……行駛中……"); } catch(InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } public class Car2 extends Car{ @Override public void move() { //分離代碼,增加業(yè)務(wù)邏輯 long startTime=System.currentTimeMillis(); System.out.println("汽車開始行駛……"); super.move(); long endTime=System.currentTimeMillis(); System.out.println("汽車結(jié)束行駛……時(shí)間:"+(endTime-startTime)+"ms"); } }
繼承方式實(shí)現(xiàn)代理
Moveablecar2=newCar2();
car2.move();
聚合方式實(shí)現(xiàn)代理
Carcar=newCar();
Moveablem=newCar3(car);
m.move();
總結(jié)
使用繼承方式不夠靈活,當(dāng)功能疊加的時(shí)候,只能臃腫的擴(kuò)展代理類;
使用聚合的方式,代理之間可以相互傳遞,靈活的組合代理;
public class CarLogProxy extends Car{ @Override public void move() { //分離代碼,增加業(yè)務(wù)邏輯 long startTime=System.currentTimeMillis(); System.out.println("日志開始……"); super.move(); long endTime=System.currentTimeMillis(); System.out.println("日志結(jié)束……"); } } public class CarTimeProxy implements Moveable { public CarTimeProxy(Car car) { super(); this.car=car; } private Carcar; @Override public void move() { //分離代碼,增加業(yè)務(wù)邏輯 long startTime=System.currentTimeMillis(); System.out.println("汽車開始行駛……"); car.move(); long endTime=System.currentTimeMillis(); System.out.println("汽車結(jié)束行駛……時(shí)間:"+(endTime-startTime)+"ms"); } } @Test: Car car =new Car(); CarTimeProxy ctp=new CarTimeProxy(car); CarLogProxy clp=new CarLogProxy(ctp); clp.move(); //還可以通過接口相互傳遞代理實(shí)例 CarLogProxy clp1=new CarLogProxy(car); CarTimeProxy ctp1=new CarTimeProxy(clp1); ctp1.move();
JDK動(dòng)態(tài)代理和CGlib動(dòng)態(tài)代理
JDK動(dòng)態(tài)代理
代理實(shí)現(xiàn)
如果不同的對(duì)象要實(shí)現(xiàn)相同功能的代理類,應(yīng)該如何處置?
此時(shí)可以試著將其集成在同一個(gè)代理類中-----動(dòng)態(tài)代理:實(shí)現(xiàn)對(duì)不同類/不同方法的代理;
大致過程如下:
Java動(dòng)態(tài)代理類位于java.lang.reflect包下,一般主要涉及到一下兩個(gè)類:
(1)InterfaceInvocationHandler:該接口中僅定義了一個(gè)方法Publicobjectinvoke(Objectobj,Methodmethod,Object[]args)
obj:一般是指代理類
method:是被代理的方法
args為該方法的參數(shù)數(shù)組。
這個(gè)抽象的方法在代理類中動(dòng)態(tài)實(shí)現(xiàn)。
(2)Proxy:該類即為動(dòng)態(tài)代理類
statixObjectnewProxyInstance(ClassLoaderloader,Class[]interfaces,InvocationHandlerh)
返回甙類類的一個(gè)實(shí)例,返回后的代理類可以當(dāng)做被代理類使用(可以使用被代理類在接口中聲明過的方法);
實(shí)現(xiàn)實(shí)例:
@ TimeHandler public class TimeHandler implements InvocationHandler { public TimeHandler(Object target) { super(); this.target = target; } private Objecttarget; /* * 參數(shù): * proxy 被代理對(duì)象 * method 被代理對(duì)象的方法 * args 方法的參數(shù) * * 返回值: * Object 方法返回值 */ @Override public Object invoke(Object proxy, Method method,Object[] args) throws Throwable { long startTime=System.currentTimeMillis(); System.out.println("汽車開始行駛……"); method.invoke(target); long endTime=System.currentTimeMillis(); System.out.println("汽車結(jié)束行駛……時(shí)間:"+(endTime-startTime)+"ms"); return null; } }
@被代理類的接口 public interface Moveable { public void move(); } @被代理的類
public class Car implements Moveable{ @Override public void move() { try { Thread.sleep(new Random().nextint(1000)); System.out.println("……行駛中……"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
@測(cè)試
public class Test { /** * JDk動(dòng)態(tài)代理的測(cè)試類 */ public static void main(String[] args) { Car car=new Car(); InvocationHandler h=new TimeHandler(car); Class<?>cls=car.getClass(); /* * loader 類加載器 * interfaces 實(shí)現(xiàn)接口 * h InvocationHandler */ Moveable m=(Moveable)Proxy.newProxyInstance(cls.getClassLoader(),cls.getInterfaces(),h); m.move(); } }
&&測(cè)試結(jié)果
梳理總結(jié)
所為的DynamicProxy是這樣一種class:
它是在運(yùn)行時(shí)生成的class,該class需要實(shí)現(xiàn)一組interface,使用動(dòng)態(tài)代理類的時(shí)候,必須實(shí)現(xiàn)InvocationHandler接口。
JDK動(dòng)態(tài)代理的一般步驟
1.創(chuàng)建一個(gè)實(shí)現(xiàn)接口InvocationHandler的類,它必須實(shí)現(xiàn)invoke()
2.創(chuàng)建被代理的類以及接口
3.調(diào)用Proxy的靜態(tài)方法,創(chuàng)建一個(gè)代理類
newProxyInstance(ClassLoaderloader,Class[]interfaces,InvocationHandlerh)
4.通過代理調(diào)用方法
CGlib動(dòng)態(tài)代理的實(shí)現(xiàn)
代理實(shí)現(xiàn)
@引入cglib-node-2.2.jar包
@CglibProxy攔截類實(shí)現(xiàn)接口MethodInterceptor:重寫intercept攔截方法
public class CglibProxy implements MethodInterceptor { private Enhancerenhancer=new Enhancer(); public Object getProxy(Class cl) { //設(shè)置創(chuàng)建子類的類 enhancer.setSuperclass(cl); enhancer.setCallback(this); return enhancer.create(); } /* * 攔截所有目標(biāo)類方法的調(diào)用 * object 目標(biāo)類的實(shí)例 * m 目標(biāo)方法的反射對(duì)象 * args 方法的參數(shù) * proxy 代理類的實(shí)例 * */ @Override public Object intercept(Object obj, Method m,Object[] args, MethodProxy proxy)throws Throwable { System.out.println("日志開始……"); //代理類調(diào)用父類的方法 proxy.invokeSuper(obj, args); System.out.println("日志結(jié)束……"); return null; } }
@被代理類Train
public class Train { public void move() { System.out.println("火車行駛中……"); } }
@測(cè)試類
public class Test { /** * cglibProxy動(dòng)態(tài)代理測(cè)試類 */ public static void main(String[] args) { CglibProxy proxy=new CglibProxy(); Train t=(Train)proxy.getProxy(Train.class); t.move(); } }
##測(cè)試結(jié)果:
梳理總結(jié)
使用CglibProxy實(shí)現(xiàn)動(dòng)態(tài)代理的一般步驟
1、創(chuàng)建類實(shí)現(xiàn)接口MethodInterceptor,并重寫intercept方法
2、創(chuàng)建被代理類
3、調(diào)用代理類自定義的方法,得到一個(gè)代理實(shí)例
4、通過代理實(shí)例調(diào)用被代理類的需要執(zhí)行的方法
比較總結(jié)
JDK動(dòng)態(tài)代理
1、只能代理實(shí)現(xiàn)了接口的類
2、沒有實(shí)現(xiàn)接口的類不能實(shí)現(xiàn)JDK的動(dòng)態(tài)代理
CGlib動(dòng)態(tài)代理
1、針對(duì)類來實(shí)現(xiàn)代理
2、對(duì)執(zhí)行目標(biāo)類產(chǎn)生一個(gè)子類,通過方法攔截技術(shù)攔截所有父類方法的調(diào)用。
模擬代理產(chǎn)生步驟
思路:
實(shí)現(xiàn)功能:通過Proxy的newProxyInstance返回代理對(duì)象
1、聲明一段源碼(動(dòng)態(tài)產(chǎn)生代理)
2、編譯源碼(JDKCompilerAPI)產(chǎn)生新的類(代理類)
3、將這個(gè)類load到內(nèi)存當(dāng)中,產(chǎn)生一個(gè)新的對(duì)象(代理對(duì)象)
4、返回代理對(duì)象
完善動(dòng)態(tài)代理實(shí)現(xiàn)
首先得到系統(tǒng)編譯器,通過編譯器得到文件管理者,然后獲取文件,然后編譯器執(zhí)行編譯任務(wù),完成編譯之后,將class文件加載到類加載器中,通過構(gòu)造方法得到實(shí)例,然后調(diào)用newInstance()接收一個(gè)對(duì)象的實(shí)例。
(1)拿到編譯器JavaCompilercompiler=ToolProvider.getSystemJavaCompiler();
(2)文件管理者StandardJavaFileManagerfileMgr=Compiler.getStandardFileManager(null,null,null);
(3)獲取文件Iterableunits=fileMgr.getJavaFileObjects(filename);
(4)編譯任務(wù)CompilationTaskt=compiler.getTask(null,fileMgr,null,null,null,units);
(5)load到內(nèi)存
ClassLoadercl=ClassLoader.getSystemClassLoader();
Classc=cl.loadClass(”com.imooc.proxy.$Proxy0”);
(6)通過代理對(duì)象的構(gòu)造器構(gòu)造實(shí)例
Constructorctr=c.getConstructor(infce);
ctr.newInstance(newCar());
-------
上說所說,內(nèi)部的業(yè)務(wù)邏輯是硬編碼的,如何實(shí)現(xiàn)真正的動(dòng)態(tài)代理,動(dòng)態(tài)的指定業(yè)務(wù)邏輯呢?
1、需要?jiǎng)?chuàng)建一個(gè)事務(wù)處理器,首先創(chuàng)建一個(gè)接口也就是InvocationHandler,為了模擬JDK,這里把接口的名字和JDK事務(wù)處理器名稱一樣,同樣寫一個(gè)方法叫做invoke(),用來表示對(duì)某個(gè)對(duì)象的某個(gè)方法進(jìn)行業(yè)務(wù)處理,所以需要把某個(gè)對(duì)象以及對(duì)象的方法作為invoke()方法的參數(shù)傳遞進(jìn)來,invoke(Objectobj,Methodmethod),方法作為參數(shù)使用到了java反射,需要把此包引入。這樣InvocationHandler接口就完成了。
2、創(chuàng)建事務(wù)處理實(shí)現(xiàn)類比如說時(shí)間代理TimerProxy,實(shí)現(xiàn)了InvocationHandler接口,這樣結(jié)構(gòu)就成了
——————TimerProxyimplementsInvocationHandler{ ————————-@override ————————-voidinvoke(Objectobj,Methodmethod){ ———————————//業(yè)務(wù)邏輯<br> —————————————method.invoke(目標(biāo)對(duì)象,參數(shù)); ————————————//業(yè)務(wù)邏輯<br> ——————————} —————————}
需要將目標(biāo)對(duì)象傳入,沒有參數(shù)可以不寫參數(shù),創(chuàng)建代理對(duì)象的構(gòu)造方法,初始化目標(biāo)對(duì)象
3、在Proxy類的newProxyInstance()方法中,除了要把目標(biāo)Class接口作為參數(shù)外,還需要把事務(wù)處理器InvocationHandler傳進(jìn)去,然后更改創(chuàng)建實(shí)例對(duì)象中硬編碼的部分用事務(wù)處理器方法替代即可。難點(diǎn)在于字符串的拼接。
總結(jié)
在我們項(xiàng)目中代理模式有自己的實(shí)際意義,比如說我們想要調(diào)用某個(gè)jar包下的某個(gè)類,可以在調(diào)用這個(gè)類之前之后添加一些特殊的業(yè)務(wù)邏輯,這種方式也叫作AOP面向切面編程。(在不改變?cè)泄δ艿幕A(chǔ)上,添加額外的功能。)
以上就是本文關(guān)于Java動(dòng)態(tài)代理(設(shè)計(jì)模式)代碼詳解的全部?jī)?nèi)容,希望對(duì)大家有所幫助。感興趣的朋友可以繼續(xù)參閱本站其他相關(guān)專題,如有不足之處,歡迎留言指出。感謝朋友們對(duì)本站的支持!
相關(guān)文章
Mybatis報(bào)Type interface *.*Mapper is not&
本文主要介紹了Mybatis報(bào)Type interface *.*Mapper is not known to the MapperRegis,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-07-07Mybatis的SqlRunner執(zhí)行流程實(shí)現(xiàn)
MyBatis提供了一個(gè)用于操作數(shù)據(jù)庫(kù)的SqlRunner工具類,對(duì)JDBC做了很好的封裝,本文主要介紹了Mybatis的SqlRunner執(zhí)行流程實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2023-10-10基于Java的度分秒坐標(biāo)轉(zhuǎn)純經(jīng)緯度坐標(biāo)的漂亮國(guó)基地信息管理的方法
本文以java語言為例,詳細(xì)介紹如何管理漂亮國(guó)的基地信息,為下一步全球的空間可視化打下堅(jiān)實(shí)的基礎(chǔ),首先介紹如何對(duì)數(shù)據(jù)進(jìn)行去重處理,然后介紹在java當(dāng)中如何進(jìn)行度分秒位置的轉(zhuǎn)換,最后結(jié)合實(shí)現(xiàn)原型進(jìn)行詳細(xì)的說明,感興趣的朋友跟隨小編一起看看吧2024-06-06基于Java代碼實(shí)現(xiàn)判斷春節(jié)、端午節(jié)、中秋節(jié)等法定節(jié)假日的方法
這篇文章主要介紹了基于Java代碼實(shí)現(xiàn)判斷春節(jié)、端午節(jié)、中秋節(jié)等法定節(jié)假日的方法 的相關(guān)資料,需要的朋友可以參考下2016-01-01使用@value注解取不到application.xml配置文件中的值問題
這篇文章主要介紹了使用@value注解取不到application.xml配置文件中的值問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03如何在Java SpringBoot項(xiàng)目中配置動(dòng)態(tài)數(shù)據(jù)源你知道嗎
這篇文章主要介紹了SpringBoot如何在運(yùn)行時(shí)動(dòng)態(tài)添加數(shù)據(jù)源,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2021-09-09springboot實(shí)現(xiàn)增加黑名單和白名單功能
本文主要介紹了springboot實(shí)現(xiàn)增加黑名單和白名單功能,就是單純的實(shí)現(xiàn)filter,然后注冊(cè)到springboot里面,在filter里面進(jìn)行黑白名單的篩選,感興趣的可以了解一下2024-05-05