java 代理模式及動(dòng)態(tài)代理機(jī)制深入分析
java 代理模式及動(dòng)態(tài)代理機(jī)制深入分析
代理設(shè)計(jì)模式
代理是一種常用的設(shè)計(jì)模式,其目的就是為其他對(duì)象提供一個(gè)代理以控制對(duì)某個(gè)對(duì)象的訪問(wèn)。代理類負(fù)責(zé)為委托類預(yù)處理消息,過(guò)濾消息并轉(zhuǎn)發(fā)消息,以及進(jìn)行消息被委托類執(zhí)行后的后續(xù)處理。
代理模式的作用是:為其他對(duì)象提供一種代理以控制對(duì)這個(gè)對(duì)象的訪問(wèn)。在某些情況下,一個(gè)客戶不想或者不能直接引用另一個(gè)對(duì)象,而代理對(duì)象可以在客戶端和目標(biāo)對(duì)象之間起到中介的作用。
代理模式一般涉及到的角色有:
抽象角色:聲明真實(shí)對(duì)象和代理對(duì)象的共同接口;
代理角色:代理對(duì)象角色內(nèi)部含有對(duì)真實(shí)對(duì)象的引用,從而可以操作真實(shí)對(duì)象,同時(shí)代理對(duì)象提供與真實(shí)對(duì)象相同的接口以便
在任何時(shí)刻都能代替真實(shí)對(duì)象。同時(shí),代理對(duì)象可以在執(zhí)行真實(shí)對(duì)象操作時(shí),附加其他的操作,相當(dāng)于對(duì)真實(shí)對(duì)象進(jìn)行封裝。
真實(shí)角色:代理角色所代表的真實(shí)對(duì)象,是我們最終要引用的對(duì)象
圖 1. 代理模式類圖

為了保持行為的一致性,代理類和委托類通常會(huì)實(shí)現(xiàn)相同的接口,所以在訪問(wèn)者看來(lái)兩者沒(méi)有絲毫的區(qū)別。通過(guò)代理類這中間一層,能有效控制對(duì)委托 類對(duì)象的直接訪問(wèn),也可以很好地隱藏和保護(hù)委托類對(duì)象,同時(shí)也為實(shí)施不同控制策略預(yù)留了空間,從而在設(shè)計(jì)上獲得了更大的靈活性。Java 動(dòng)態(tài)代理機(jī)制以巧妙的方式近乎完美地實(shí)踐了代理模式的設(shè)計(jì)理念。
java動(dòng)態(tài)代理
相關(guān)的類和接口
要了解 Java 動(dòng)態(tài)代理的機(jī)制,首先需要了解以下相關(guān)的類或接口:
· java.lang.reflect.Proxy:這是 Java 動(dòng)態(tài)代理機(jī)制的主類,它提供了一組靜態(tài)方法來(lái)為一組接口動(dòng)態(tài)地生成代理類及其對(duì)象。
清單 1. Proxy 的靜態(tài)方法
// 方法 1: 該方法用于獲取指定代理對(duì)象所關(guān)聯(lián)的調(diào)用處理器 static InvocationHandler getInvocationHandler(Object proxy) // 方法 2:該方法用于獲取關(guān)聯(lián)于指定類裝載器和一組接口的動(dòng)態(tài)代理類的類對(duì)象 static Class getProxyClass(ClassLoader loader, Class[] interfaces) // 方法 3:該方法用于判斷指定類對(duì)象是否是一個(gè)動(dòng)態(tài)代理類 static boolean isProxyClass(Class cl) // 方法 4:該方法用于為指定類裝載器、一組接口及調(diào)用處理器生成動(dòng)態(tài)代理類實(shí)例 static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
java.lang.reflect.InvocationHandler:這是調(diào)用處理器接口,它自定義了一個(gè) invoke 方法,用于集中處理在動(dòng)態(tài)代理類對(duì)象上的方法調(diào)用,通常在該方法中實(shí)現(xiàn)對(duì)委托類的代理訪問(wèn)。
清單 2. InvocationHandler 的核心方法
// 該方法負(fù)責(zé)集中處理動(dòng)態(tài)代理類上的所有方法調(diào)用。第一個(gè)參數(shù)既是代理類實(shí)例,第二個(gè)參數(shù)是被調(diào)用的方法對(duì)象 // 第三個(gè)方法是調(diào)用參數(shù)。調(diào)用處理器根據(jù)這三個(gè)參數(shù)進(jìn)行預(yù)處理或分派到委托類實(shí)例上發(fā)射執(zhí)行 Object invoke(Object proxy, Method method, Object[] args)
每次生成動(dòng)態(tài)代理類對(duì)象時(shí)都需要指定一個(gè)實(shí)現(xiàn)了該接口的調(diào)用處理器對(duì)象(參見(jiàn) Proxy 靜態(tài)方法 4 的第三個(gè)參數(shù))。
· java.lang.ClassLoader:這是類裝載器類,負(fù)責(zé)將類的字節(jié)碼裝載到 Java 虛擬機(jī)(JVM)中并為其定義類對(duì)象,然后該類才能被使用。Proxy 靜態(tài)方法生成動(dòng)態(tài)代理類同樣需要通過(guò)類裝載器來(lái)進(jìn)行裝載才能使用,它與普通類的唯一區(qū)別就是其字節(jié)碼是由 JVM 在運(yùn)行時(shí)動(dòng)態(tài)生成的而非預(yù)存在于任何個(gè) .class 文件中。
每次生成動(dòng)態(tài)代理類對(duì)象時(shí)都需要指定一個(gè)類裝載器對(duì)象(參見(jiàn) Proxy 靜態(tài)方法 4 的第一個(gè)參數(shù))
代理機(jī)制及其特點(diǎn)
首先讓我們來(lái)了解一下如何使用 Java 動(dòng)態(tài)代理。具體有如下四步驟:
1. 通過(guò)實(shí)現(xiàn) InvocationHandler 接口創(chuàng)建自己的調(diào)用處理器;
2. 通過(guò)為 Proxy 類指定 ClassLoader 對(duì)象和一組 interface 來(lái)創(chuàng)建動(dòng)態(tài)代理類;
3. 通過(guò)反射機(jī)制獲得動(dòng)態(tài)代理類的構(gòu)造函數(shù),其唯一參數(shù)類型是調(diào)用處理器接口類型;
4. 通過(guò)構(gòu)造函數(shù)創(chuàng)建動(dòng)態(tài)代理類實(shí)例,構(gòu)造時(shí)調(diào)用處理器對(duì)象作為參數(shù)被傳入。
清單 3. 動(dòng)態(tài)代理對(duì)象創(chuàng)建過(guò)程
// InvocationHandlerImpl 實(shí)現(xiàn)了 InvocationHandler 接口,并能實(shí)現(xiàn)方法調(diào)用從代理類到委托類的分派轉(zhuǎn)發(fā)
// 其內(nèi)部通常包含指向委托類實(shí)例的引用,用于真正執(zhí)行分派轉(zhuǎn)發(fā)過(guò)來(lái)的方法調(diào)用
InvocationHandler handler = new InvocationHandlerImpl(..);
// 通過(guò) Proxy 為包括 Interface 接口在內(nèi)的一組接口動(dòng)態(tài)創(chuàng)建代理類的類對(duì)象
Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... });
// 通過(guò)反射從生成的類對(duì)象獲得構(gòu)造函數(shù)對(duì)象
Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class });
// 通過(guò)構(gòu)造函數(shù)對(duì)象創(chuàng)建動(dòng)態(tài)代理類實(shí)例
Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });
實(shí)際使用過(guò)程更加簡(jiǎn)單,因?yàn)?Proxy 的靜態(tài)方法 newProxyInstance 已經(jīng)為我們封裝了步驟 2 到步驟 4 的過(guò)程,所以簡(jiǎn)化后的過(guò)程如
清單 4. 簡(jiǎn)化的動(dòng)態(tài)代理對(duì)象創(chuàng)建過(guò)程
// InvocationHandlerImpl 實(shí)現(xiàn)了 InvocationHandler 接口,并能實(shí)現(xiàn)方法調(diào)用從代理類到委托類的分派轉(zhuǎn)發(fā)
InvocationHandler handler = new InvocationHandlerImpl(..);
// 通過(guò) Proxy 直接創(chuàng)建動(dòng)態(tài)代理類實(shí)例
Interface proxy = (Interface)Proxy.newProxyInstance( classLoader,
new Class[] { Interface.class },
handler );
下面我們來(lái)看一個(gè)簡(jiǎn)單實(shí)現(xiàn)動(dòng)態(tài)代理的例子:
1.代理類和真實(shí)類接口:
public interface Subject
{
public void request();
}
2.真實(shí)類:
public class RealSubject implements Subject
{
public void request()
{
System.out.println("From real subject!");
}}
3.具體代理類:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class DynamicSubject implements InvocationHandler
{
private Object sub;
public DynamicSubject(Object obj)
{
this.sub = obj;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable
{
System.out.println("before calling: " + method);
method.invoke(sub, args);
System.out.println(args == null);
System.out.println("after calling: " + method);
return null;
}
注:該代理類的內(nèi)部屬性是Object類型,實(shí)際使用的時(shí)候通過(guò)該類的構(gòu)造方法傳遞進(jìn)來(lái)一個(gè)對(duì)象。 此外,該類還實(shí)現(xiàn)了invoke方法,該方法中的method.invoke其實(shí)就是調(diào)用被代理對(duì)象的將要 執(zhí)行的方法,方法參數(shù)是sub,表示該方法從屬于sub,通過(guò)動(dòng)態(tài)代理類,我們可以在執(zhí)行真實(shí)對(duì)象的方法前后加入自己的一些額外方法。
4.客戶端調(diào)用示例:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Client
{
public static void main(String[] args)
{
RealSubject realSubject = new RealSubject();
InvocationHandler handler = new DynamicSubject(realSubject);
Class<?> classType = handler.getClass();
// 下面的代碼一次性生成代理
Subject subject = (Subject) Proxy.newProxyInstance(classType
.getClassLoader(), realSubject.getClass().getInterfaces(),
handler);
subject.request();
System.out.println(subject.getClass());
}
}
接下來(lái)讓我們來(lái)了解一下 Java 動(dòng)態(tài)代理機(jī)制 Proxy 的構(gòu)造方法:
清單 6. Proxy 構(gòu)造方法
// 由于 Proxy 內(nèi)部從不直接調(diào)用構(gòu)造函數(shù),所以 private 類型意味著禁止任何調(diào)用
private Proxy() {}
// 由于 Proxy 內(nèi)部從不直接調(diào)用構(gòu)造函數(shù),所以 protected 意味著只有子類可以調(diào)用
protected Proxy(InvocationHandler h) {this.h = h;}
接著,可以快速瀏覽一下 newProxyInstance 方法,因?yàn)槠湎喈?dāng)簡(jiǎn)單:
清單 7. Proxy 靜態(tài)方法 newProxyInstance
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException {
// 檢查 h 不為空,否則拋異常
if (h == null) {
throw new NullPointerException();
}
// 獲得與制定類裝載器和一組接口相關(guān)的代理類類型對(duì)象
Class cl = getProxyClass(loader, interfaces);
// 通過(guò)反射獲取構(gòu)造函數(shù)對(duì)象并生成代理類實(shí)例
try {
Constructor cons = cl.getConstructor(constructorParams);
return (Object) cons.newInstance(new Object[] { h });
} catch (NoSuchMethodException e) { throw new InternalError(e.toString());
} catch (IllegalAccessException e) { throw new InternalError(e.toString());
} catch (InstantiationException e) { throw new InternalError(e.toString());
} catch (InvocationTargetException e) { throw new InternalError(e.toString());
}
}
由此可見(jiàn),動(dòng)態(tài)代理真正的關(guān)鍵是在 getProxyClass 方法,該方法負(fù)責(zé)為一組接口動(dòng)態(tài)地生成代理類類型對(duì)象。
有很多條理由,人們可以否定對(duì) class 代理的必要性,但是同樣有一些理由,相信支持 class 動(dòng)態(tài)代理會(huì)更美好。接口和類的劃分,本就不是很明顯,只是到了 Java 中才變得如此的細(xì)化。如果只從方法的聲明及是否被定義來(lái)考量,有一種兩者的混合體,它的名字叫抽象類。實(shí)現(xiàn)對(duì)抽象類的動(dòng)態(tài)代理,相信也有其內(nèi)在的價(jià)值。此 外,還有一些歷史遺留的類,它們將因?yàn)闆](méi)有實(shí)現(xiàn)任何接口而從此與動(dòng)態(tài)代理永世無(wú)緣。如此種種,不得不說(shuō)是一個(gè)小小的遺憾。
但是,不完美并不等于不偉大,偉大是一種本質(zhì),Java 動(dòng)態(tài)代理就是佐例。
感謝閱讀,希望能幫助到大家,謝謝大家對(duì)本站的支持!
- Java簡(jiǎn)單實(shí)現(xiàn)動(dòng)態(tài)代理模式過(guò)程解析
- Java代理模式實(shí)例詳解【靜態(tài)代理與動(dòng)態(tài)代理】
- Java動(dòng)態(tài)代理模式的深入揭秘
- Java設(shè)計(jì)模式之動(dòng)態(tài)代理模式實(shí)例分析
- JAVA動(dòng)態(tài)代理模式(從現(xiàn)實(shí)生活角度理解代碼原理)
- 詳解java動(dòng)態(tài)代理模式
- java代理模式與動(dòng)態(tài)代理模式詳解
- 代理模式之Java動(dòng)態(tài)代理實(shí)現(xiàn)方法
- Java代理模式與動(dòng)態(tài)代理之間的關(guān)系以及概念
相關(guān)文章
Spring Boot 中的 Spring Cloud Feign的原
Spring Cloud Feign 是 Spring Cloud 中的一個(gè)組件,它可以幫助我們實(shí)現(xiàn)聲明式的 REST 客戶,這篇文章主要介紹了Spring Boot 中的 Spring Cloud Feign,需要的朋友可以參考下2023-07-07
Java集合Map的clear與new Map區(qū)別詳解
這篇文章主要介紹了Java集合Map的clear與new Map區(qū)別詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04
java連接MySQl數(shù)據(jù)庫(kù)實(shí)例代碼
這篇文章介紹了java連接MySQl數(shù)據(jù)庫(kù)實(shí)例代碼,有需要的朋友可以參考一下2013-10-10
Springboot mybatisplus如何解決分頁(yè)組件IPage失效問(wèn)題
這篇文章主要介紹了Springboot mybatisplus如何解決分頁(yè)組件IPage失效問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08
Java Web制作登錄驗(yàn)證碼實(shí)現(xiàn)代碼解析
這篇文章主要介紹了Java Web制作登錄驗(yàn)證碼實(shí)現(xiàn)代碼解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09
Java基于drools做規(guī)則校驗(yàn)的實(shí)現(xiàn)
工作中需要開(kāi)發(fā)一個(gè)規(guī)則服務(wù),提供各種規(guī)則,本文主要介紹了Java基于drools做規(guī)則校驗(yàn)的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2024-03-03
聊聊springmvc中controller的方法的參數(shù)注解方式
本篇文章主要介紹了聊聊springmvc中controller的方法的參數(shù)注解方式,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10
JAVA使用quartz添加定時(shí)任務(wù),并依賴注入對(duì)象操作
這篇文章主要介紹了JAVA使用quartz添加定時(shí)任務(wù),并依賴注入對(duì)象操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-09-09
Spring security自定義用戶認(rèn)證流程詳解
這篇文章主要介紹了Spring security自定義用戶認(rèn)證流程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03

