溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Java中的動態(tài)代理是什么

發(fā)布時間:2021-08-30 11:35:19 來源:億速云 閱讀:144 作者:chen 欄目:編程語言

本篇內(nèi)容介紹了“Java中的動態(tài)代理是什么”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

十分鐘理解 Java 中的動態(tài)代理

一、概述

1. 什么是代理

我們大家都知道微商代理,簡單地說就是代替廠家賣商品,廠家“委托”代理為其銷售商品。關于微商代理,首先我們從他們那里買東西時通常不知道背后的廠家究竟是誰,也就是說,“委托者”對我們來說是不可見的;其次,微商代理主要以朋友圈的人為目標客戶,這就相當于為廠家做了一次對客戶群體的“過濾”。我們把微商代理和廠家進一步抽象,前者可抽象為代理類,后者可抽象為委托類(被代理類)。通過使用代理,通常有兩個優(yōu)點,并且能夠分別與我們提到的微商代理的兩個特點對應起來:

優(yōu)點一:可以隱藏委托類的實現(xiàn);

優(yōu)點二:可以實現(xiàn)客戶與委托類間的解耦,在不修改委托類代碼的情況下能夠做一些額外的處理。

2. 靜態(tài)代理

若代理類在程序運行前就已經(jīng)存在,那么這種代理方式被成為 靜態(tài)代理 ,這種情況下的代理類通常都是我們在Java代碼中定義的。 通常情況下, 靜態(tài)代理中的代理類和委托類會實現(xiàn)同一接口或是派生自相同的父類。 下面我們用Vendor類代表生產(chǎn)廠家,BusinessAgent類代表微商代理,來介紹下靜態(tài)代理的簡單實現(xiàn),委托類和代理類都實現(xiàn)了Sell接口,Sell接口的定義如下:


  1. public interface Sell { void sell(); void ad(); 

  2.  

Vendor類的定義如下:


  1. public class Vendor implements Sell { public void sell() { 

  2.  

  3. System.out.println("In sell method"); 

  4.  

  5. } public void ad() { 

  6.  

  7. System,out.println("ad method") 

  8.  

  9.  

代理類BusinessAgent的定義如下:


  1. public class Vendor implements Sell { public void sell() { 

  2.  

  3. System.out.println("In sell method"); 

  4.  

  5. } public void ad() { 

  6.  

  7. System,out.println("ad method") 

  8.  

  9.  

從BusinessAgent類的定義我們可以了解到,靜態(tài)代理可以通過聚合來實現(xiàn),讓代理類持有一個委托類的引用即可。

下面我們考慮一下這個需求:給Vendor類增加一個過濾功能,只賣貨給大學生。通過靜態(tài)代理,我們無需修改Vendor類的代碼就可以實現(xiàn),只需在BusinessAgent類中的sell方法中添加一個判斷即可如下所示:
<ol start="1" class="dp-j" white-space:normal;margin:0px 0px 1px 45px !important;">

  • public class BusinessAgent implements Sell {  

  •     private Vendor mVendor;  

  •    

  •     public BusinessAgent(Vendor vendor) {  

  •         mVendor = vendor;  

  •     }  

  •    

  •     public void sell() { mVendor.sell(); }  

  •     public void ad() { mVendor.ad(); }  


這對應著我們上面提到的使用代理的第二個優(yōu)點:可以實現(xiàn)客戶與委托類間的解耦,在不修改委托類代碼的情況下能夠做一些額外的處理。靜態(tài)代理的局限在于運行前必須編寫好代理類,下面我們重點來介紹下運行時生成代理類的動態(tài)代理方式。

二、動態(tài)代理

1. 什么是動態(tài)代理

代理類在程序運行時創(chuàng)建的代理方式被成為 動態(tài)代理。 也就是說,這種情況下,代理類并不是在Java代碼中定義的,而是在運行時根據(jù)我們在Java代碼中的“指示”動態(tài)生成的。相比于靜態(tài)代理, 動態(tài)代理的優(yōu)勢在于可以很方便的對代理類的函數(shù)進行統(tǒng)一的處理,而不用修改每個代理類的函數(shù)。 這么說比較抽象,下面我們結合一個實例來介紹一下動態(tài)代理的這個優(yōu)勢是怎么體現(xiàn)的。

現(xiàn)在,假設我們要實現(xiàn)這樣一個需求:在執(zhí)行委托類中的方法之前輸出“before”,在執(zhí)行完畢后輸出“after”。我們還是以上面例子中的Vendor類作為委托類,BusinessAgent類作為代理類來進行介紹。首先我們來使用靜態(tài)代理來實現(xiàn)這一需求,相關代碼如下:


  1. public class BusinessAgent implements Sell { 

  2.  

  3. private Vendor mVendor; 

  4.  

  5. public BusinessAgent(Vendor vendor) { 

  6.  

  7. this.mVendor = vendor; 

  8.  

  9.  

  10. public void sell() { 

  11.  

  12. System.out.println("before"); 

  13.  

  14. mVendor.sell(); 

  15.  

  16. System.out.println("after"); 

  17.  

  18.  

  19. public void ad() { 

  20.  

  21. System.out.println("before"); 

  22.  

  23. mVendor.ad(); 

  24.  

  25. System.out.println("after"); 

  26.  

  27.  

從以上代碼中我們可以了解到,通過靜態(tài)代理實現(xiàn)我們的需求需要我們在每個方法中都添加相應的邏輯,這里只存在兩個方法所以工作量還不算大,假如Sell接口中包含上百個方法呢?這時候使用靜態(tài)代理就會編寫許多冗余代碼。通過使用動態(tài)代理,我們可以做一個“統(tǒng)一指示”,從而對所有代理類的方法進行統(tǒng)一處理,而不用逐一修改每個方法。下面我們來具體介紹下如何使用動態(tài)代理方式實現(xiàn)我們的需求。

2. 使用動態(tài)代理

(1)InvocationHandler接口

在使用動態(tài)代理時,我們需要定義一個位于代理類與委托類之間的中介類,這個中介類被要求實現(xiàn)InvocationHandler接口,這個接口的定義如下:


  1. public interface InvocationHandler { 

  2.  

  3. Object invoke(Object proxy, Method method, Object[] args); 

  4.  

從InvocationHandler這個名稱我們就可以知道,實現(xiàn)了這個接口的中介類用做“調(diào)用處理器”。當我們調(diào)用代理類對象的方法時,這個“調(diào)用”會轉送到invoke方法中,代理類對象作為proxy參數(shù)傳入,參數(shù)method標識了我們具體調(diào)用的是代理類的哪個方法,args為這個方法的參數(shù)。這樣一來,我們對代理類中的所有方法的調(diào)用都會變?yōu)閷nvoke的調(diào)用,這樣我們可以在invoke方法中添加統(tǒng)一的處理邏輯(也可以根據(jù)method參數(shù)對不同的代理類方法做不同的處理)。因此我們只需在中介類的invoke方法實現(xiàn)中輸出“before”,然后調(diào)用委托類的invoke方法,再輸出“after”。下面我們來一步一步具體實現(xiàn)它。

(2)委托類的定義

動態(tài)代理方式下,要求委托類必須實現(xiàn)某個接口,這里我們實現(xiàn)的是Sell接口。委托類Vendor類的定義如下:


  1. public class Vendor implements Sell { 

  2.  

  3. public void sell() { 

  4.  

  5. System.out.println("In sell method"); 

  6.  

  7.  

  8. public void ad() { 

  9.  

  10. System,out.println("ad method") 

  11.  

  12.  

(3)中介類

上面我們提到過,中介類必須實現(xiàn)InvocationHandler接口,作為調(diào)用處理器”攔截“對代理類方法的調(diào)用。中介類的定義如下:


  1. public class DynamicProxy implements InvocationHandler { 

  2.  

  3. private Object obj; //obj為委托類對象; 

  4.  

  5. public DynamicProxy(Object obj) { 

  6.  

  7. this.obj = obj; 

  8.  

  9.  

  10. @Override 

  11.  

  12. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 

  13.  

  14. System.out.println("before"); 

  15.  

  16. Object result = method.invoke(obj, args); 

  17.  

  18. System.out.println("after"); 

  19.  

  20. return result; 

  21.  

  22.  

從以上代碼中我們可以看到,中介類持有一個委托類對象引用,在invoke方法中調(diào)用了委托類對象的相應方法(第11行),看到這里是不是覺得似曾相識?通過聚合方式持有委托類對象引用,把外部對invoke的調(diào)用最終都轉為對委托類對象的調(diào)用。這不就是我們上面介紹的靜態(tài)代理的一種實現(xiàn)方式嗎?實際上,中介類與委托類構成了靜態(tài)代理關系,在這個關系中,中介類是代理類,委托類就是委托類; 代理類與中介類也構成一個靜態(tài)代理關系,在這個關系中,中介類是委托類,代理類是代理類。也就是說,動態(tài)代理關系由兩組靜態(tài)代理關系組成,這就是動態(tài)代理的原理。下面我們來介紹一下如何”指示“以動態(tài)生成代理類。

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

動態(tài)生成代理類的相關代碼如下:


  1. public class Main { 

  2.  

  3. public static void main(String[] args) { 

  4.  

  5. //創(chuàng)建中介類實例 

  6.  

  7. DynamicProxy inter = new DynamicProxy(new Vendor()); 

  8.  

  9. //加上這句將會產(chǎn)生一個$Proxy0.class文件,這個文件即為動態(tài)生成的代理類文件 

  10.  

  11. System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true"); 

  12.  

  13. //獲取代理類實例sell 

  14.  

  15. Sell sell = (Sell)(Proxy.newProxyInstance(Sell.class.getClassLoader(), new Class[] {Sell.class}, inter)); 

  16.  

  17. //通過代理類對象調(diào)用代理類方法,實際上會轉到invoke方法調(diào)用 

  18.  

  19. sell.sell(); 

  20.  

  21. sell.ad(); 

  22.  

  23.  

在以上代碼中,我們調(diào)用Proxy類的newProxyInstance方法來獲取一個代理類實例。這個代理類實現(xiàn)了我們指定的接口并且會把方法調(diào)用分發(fā)到指定的調(diào)用處理器。這個方法的聲明如下:


  1. public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException 

方法的三個參數(shù)含義分別如下:

loader:定義了代理類的ClassLoder;

interfaces:代理類實現(xiàn)的接口列表

h:調(diào)用處理器,也就是我們上面定義的實現(xiàn)了InvocationHandler接口的類實例

我們運行一下,看看我們的動態(tài)代理是否能正常工作。我這里運行后的輸出為:

Java中的動態(tài)代理是什么

說明我們的動態(tài)代理確實奏效了。

上面我們已經(jīng)簡單提到過動態(tài)代理的原理,這里再簡單的總結下:首先通過newProxyInstance方法獲取代理類實例,而后我們便可以通過這個代理類實例調(diào)用代理類的方法,對代理類的方法的調(diào)用實際上都會調(diào)用中介類(調(diào)用處理器)的invoke方法,在invoke方法中我們調(diào)用委托類的相應方法,并且可以添加自己的處理邏輯。

先看一下代理模式,這個應該是設計模式中最簡單的一個了,類圖

Java中的動態(tài)代理是什么

  代理模式最大的特點就是代理類和實際業(yè)務類實現(xiàn)同一個接口(或繼承同一父類),代理對象持有一個實際對象的引用,外部調(diào)用時操作的是代理對象,而在代理對象的內(nèi)部實現(xiàn)中又會去調(diào)用實際對象的操作

 Java動態(tài)代理其實內(nèi)部也是通過Java反射機制來實現(xiàn)的,即已知的一個對象,然后在運行時動態(tài)調(diào)用其方法,這樣在調(diào)用前后作一些相應的處理,這樣說的比較籠統(tǒng),舉個簡單的例子

   比如我們在應用中有這樣一個需求,在對某個類的一個方法的調(diào)用前和調(diào)用后都要做一下日志操作,

一個普通的接口

[java] view plain copy  

  1. public interface AppService {  

  2.       public boolean createApp(String name);  

  3. }  

該接口的默認實現(xiàn)類

[java] view plain copy  

  1. public class AppServiceImpl implements AppService {  

  2.     public boolean createApp(String name) {  

  3.         System.out.println("App["+name+"] has been created.");  

  4.         return true;  

  5.     }  

  6. }  

日志處理器

[java] view plain copy  

  1. public class LoggerInterceptor implements InvocationHandler {//注意實現(xiàn)這個Handler接口  

  2.     private Object target;//目標對象的引用,這里設計成Object類型,更具通用性  

  3.     public LoggerInterceptor(Object target){  

  4.         this.target = target;  

  5.     }  

  6.     public Object invoke(Object proxy, Method method, Object[] arg)  

  7.             throws Throwable {  

  8.         System.out.println("Entered "+target.getClass().getName()+"-"+method.getName()+",with arguments{"+arg[0]+"}");  

  9.         Object result = method.invoke(target, arg);//調(diào)用目標對象的方法  

  10.         System.out.println("Before return:"+result);  

  11.         return result;  

  12.     }  

  13. }  

外部調(diào)用

[java] view plain copy  

  1. public class Main {  

  2.     public static void main(String[] args) {  

  3.         AppService target = new AppServiceImpl();//生成目標對象  

  4.         //接下來創(chuàng)建代理對象  

  5.         AppService proxy = (AppService) Proxy.newProxyInstance(  

  6.                 target.getClass().getClassLoader(),  

  7.                 target.getClass().getInterfaces(), new LoggerInterceptor(target));  

  8.         proxy.createApp("Kevin Test");  

  9.     }  

  10. }  

“Java中的動態(tài)代理是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關的知識可以關注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。

AI