更新時(shí)間:2022-06-27 10:55:18 來源:動(dòng)力節(jié)點(diǎn) 瀏覽1698次
Java的動(dòng)態(tài)代理模式在實(shí)踐中的使用場(chǎng)景非常廣泛,比如大部分場(chǎng)景的Spring AOP、Java注解的獲取、日志記錄、用戶認(rèn)證等等。動(dòng)力節(jié)點(diǎn)小編帶你了解代理模式、靜態(tài)代理、原生動(dòng)態(tài)基于JDK的代理。
無論你是學(xué)習(xí)靜態(tài)代理還是動(dòng)態(tài)代理,我們都需要先了解代理模型。
先看百度百科的定義:
代理模式的定義:為其他對(duì)象提供一個(gè)代理來控制對(duì)這個(gè)對(duì)象的訪問。在某些情況下,一個(gè)對(duì)象不合適或不能直接引用另一個(gè)對(duì)象,而代理對(duì)象可以充當(dāng)客戶端和目標(biāo)對(duì)象之間的中介。
直接看定義可能有些難以理解,下面就用生活中的具體例子來說明吧。
我們都去超市買過貨,超市從廠家買后賣給我們。我們通常不知道貨物要經(jīng)過多少道工序才能到達(dá)超市。
在這個(gè)過程中,意味著制造商“委托”超市銷售商品,而我們(實(shí)物)是看不見的。超市(代理對(duì)象)充當(dāng)制造商的“代理人”與我們互動(dòng)。
同時(shí),超市還可以根據(jù)具體的銷售情況進(jìn)行折扣處理,豐富代理商的功能。
使用代理模型,我們可以做兩件事:
1.隱藏委托類的實(shí)現(xiàn)。
2. 將客戶端與委托類解耦,在不改變委托類代碼的情況下增加一些額外的功能(日志、權(quán)限)。
在編程過程中,我們可以定義三種類型的對(duì)象:
Subject(抽象主題角色):定義代理類和真實(shí)主題的公共外部方法,也是代理類代理真實(shí)主題的一種方式。例如:廣告、銷售等。
RealSubject:真正實(shí)現(xiàn)業(yè)務(wù)邏輯的類。例如Vendor,實(shí)現(xiàn)廣告、銷售等方法。
代理:用于代理和封裝真實(shí)的主題。例如廣告和銷售等超時(shí)也實(shí)現(xiàn)。
上述三個(gè)角色的類圖如下:

靜態(tài)代理是指在程序運(yùn)行之前已經(jīng)存在代理類,這種情況下代理類通常是在Java代碼中定義的。
讓我們用一個(gè)具體的例子來演示一個(gè)靜態(tài)代理。
首先定義一組接口Sells,提供廣告、銷售等功能。然后提供Vendor類(制造商,代理對(duì)象)和Shop(超市,代理類),分別實(shí)現(xiàn)Sell接口。
Sell 接口定義如下:
/**
* 委托和代理類都實(shí)現(xiàn)了 Sell 接口
* @作者秒
* @版本 1.0
* @日期 2020/3/21 上午 9:30
**/
公共接口賣{
/**
* 賣
*/
無效出售();
/**
* 廣告
*/
無效廣告();
}
Vendor 類定義如下:
/**
* 供應(yīng)商
* @作者秒
* @版本 1.0
* @日期 2020/3/21 上午 9:30
**/
公共類供應(yīng)商實(shí)現(xiàn) Sell{
@覆蓋
公共無效出售(){
System.out.println("店鋪賣貨");
}
@覆蓋
公共無效廣告(){
System.out.println("店鋪廣告商品");
}
}
Shop 類定義如下:
/**
* 超市、代理商
* @作者秒
* @版本 1.0
* @日期 2020/3/21 上午 9:30
**/
公共類 Shop 實(shí)現(xiàn) Sell{
私人出售出售;
公共商店(賣賣){
this.sell = 賣;
}
@覆蓋
公共無效出售(){
System.out.println("代理類店鋪,處理銷售");
出售.sell();
}
@覆蓋
公共無效廣告(){
System.out.println("代理類店鋪,處理廣告");
sell.ad();
}
}
代理類Shop通過聚合持有對(duì)代理類Vendor的引用,并在對(duì)應(yīng)的方法中調(diào)用對(duì)應(yīng)的Vendor方法。我們可以在hop類中增加一些額外的處理,比如過濾購買用戶、記錄日志等。
讓我們看看代理類是如何在客戶端中使用的。
/**
* 靜態(tài)代理類測(cè)試方法
* @作者秒
* @版本 1.0
* @日期 2020 年 3 月 21 日上午 9 點(diǎn) 33 分
**/
公共類靜態(tài)代理 {
公共靜態(tài)無效主要(字符串[]參數(shù)){
// 供應(yīng)商 - 代理類
供應(yīng)商 vendor = new Vendor();
// 創(chuàng)建供應(yīng)商的代理類 Shop
賣賣=新店(供應(yīng)商);
// 客戶端正在使用代理類 Shop。
sell.ad();
出售.sell();
}
}
在上面的代碼中,客戶看到的是Sell接口提供的功能,這個(gè)功能是Shop提供的。我們可以在不影響代理類Vendor的情況下,對(duì)hop進(jìn)行修改或添加一些東西。
靜態(tài)代理實(shí)現(xiàn)簡(jiǎn)單,不會(huì)侵入原始代碼,但是當(dāng)場(chǎng)景復(fù)雜時(shí),靜態(tài)代理有以下缺點(diǎn):
1.當(dāng)需要代理多個(gè)類時(shí),代理對(duì)象實(shí)現(xiàn)與目標(biāo)對(duì)象一致的接口?;蛘?,只維護(hù)一個(gè)代理類來實(shí)現(xiàn)多個(gè)接口,但這會(huì)導(dǎo)致代理類變得太大。或者,創(chuàng)建多個(gè)新的代理類,但這會(huì)導(dǎo)致代理類過多。
2.當(dāng)一個(gè)接口需要增加、刪除或修改方法時(shí),目標(biāo)對(duì)象和代理類都需要同時(shí)修改,不易維護(hù)。
因此,動(dòng)態(tài)代理就派上用場(chǎng)了。
動(dòng)態(tài)代理是指在程序運(yùn)行時(shí)創(chuàng)建代理類的方式。這種情況下,代理類不是在Java代碼中定義的,而是在運(yùn)行時(shí)根據(jù)Java代碼中的指令動(dòng)態(tài)生成的。
動(dòng)態(tài)代理相對(duì)于靜態(tài)代理的優(yōu)勢(shì)在于,很容易統(tǒng)一代理類的功能,而不需要修改各個(gè)代理類的功能。
動(dòng)態(tài)代理的實(shí)現(xiàn)通常有兩種方式:JDK原生動(dòng)態(tài)代理和CGLIB動(dòng)態(tài)代理。這里我們以JDK的原生動(dòng)態(tài)代理為例。
JDK動(dòng)態(tài)代理主要涉及兩個(gè)類:java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler。
InvocationHandler 接口定義了以下方法:
/**
* 呼叫處理程序
*/
公共接口 InvocationHandler {
對(duì)象調(diào)用(對(duì)象代理,方法方法,對(duì)象[] args);
}
顧名思義,實(shí)現(xiàn)這個(gè)接口的中介類被用作“調(diào)用處理器”。當(dāng)一個(gè)代理類對(duì)象的方法被調(diào)用時(shí),這個(gè)“調(diào)用”被轉(zhuǎn)發(fā)給invoke方法。代理類對(duì)象作為代理參數(shù)傳入。參數(shù)method標(biāo)識(shí)調(diào)用了代理類的哪個(gè)方法,args是方法的參數(shù)。這樣對(duì)代理類中所有方法的調(diào)用都變成invoke調(diào)用,可以給invoke方法(或不同的方法)添加統(tǒng)一的處理邏輯代理類方法可以根據(jù)方法參數(shù)進(jìn)行不同的處理)。
Proxy 類用于獲取與指定代理對(duì)象關(guān)聯(lián)的調(diào)用處理程序。
下面以添加日志為例演示動(dòng)態(tài)代理。
導(dǎo)入 java.lang.reflect.InvocationHandler;
導(dǎo)入java.lang.reflect.Method;
導(dǎo)入 java.util.Date;
公共類 LogHandler 實(shí)現(xiàn) InvocationHandler {
對(duì)象目標(biāo);// 代理對(duì)象,實(shí)際方法執(zhí)行者
公共日志處理程序(對(duì)象目標(biāo)){
this.target = 目標(biāo);
}
@覆蓋
公共對(duì)象調(diào)用(對(duì)象代理,方法方法,對(duì)象 [] args)拋出 Throwable {
前();
對(duì)象結(jié)果 = method.invoke(target, args); // 調(diào)用目標(biāo)的methodmethod方法
后();
返回結(jié)果;// 返回方法的執(zhí)行結(jié)果
}
// 在調(diào)用方法調(diào)用之前執(zhí)行
私人無效之前(){
System.out.println(String.format("日志開始時(shí)間[%s]", new Date()));
}
//調(diào)用invoke方法后執(zhí)行
私人無效后(){
System.out.println(String.format("日志結(jié)束時(shí)間[%s]", new Date()));
}
}
客戶端編寫者使用動(dòng)態(tài)代理代碼如下:
導(dǎo)入 java.lang.reflect.Proxy;
/**
* 動(dòng)態(tài)代理測(cè)試
*
* @作者秒
* @版本 1.0
* @日期 2020/3/21 上午 10:40
**/
公共類 DynamicProxyMain {
公共靜態(tài)無效主要(字符串[]參數(shù)){
// 創(chuàng)建中介類的實(shí)例
LogHandler logHandler = new LogHandler(new Vendor());
// 設(shè)置此變量以默認(rèn)名稱保存動(dòng)態(tài)代理類 $Proxy0.class
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// 獲取代理類實(shí)例 Sell
Sell sell = (Sell) (Proxy.newProxyInstance(Sell.class.getClassLoader(), new Class[]{Sell.class}, logHandler));
// 通過代理類對(duì)象調(diào)用代理類方法實(shí)際上是去invoke方法調(diào)用
出售.sell();
sell.ad();
}
}
執(zhí)行后,打印日志如下:
方法sell的調(diào)用日志處理
店鋪賣貨
方法銷售的記錄
日志處理調(diào)用方法廣告
店鋪廣告商品
調(diào)用方法廣告的日志處理
經(jīng)過上面的驗(yàn)證,我們發(fā)現(xiàn)我們已經(jīng)成功的為我們的代理類在方法執(zhí)行前后添加了日志。
為了查看上面示例中生成的動(dòng)態(tài)代理類的代碼,我們添加了以下屬性設(shè)置(需要在生產(chǎn)環(huán)境中刪除)。
// 設(shè)置此變量以默認(rèn)名稱保存動(dòng)態(tài)代理類 $Proxy0.class
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
所以,我們?cè)趫?zhí)行main方法之后,也生成了一個(gè)名為$Proxy0.class的類文件。通過反編譯,可以看到如下代碼:
包 com.sun.proxy;
進(jìn)口com.choupangxia.proxy.Sell;
導(dǎo)入 java.lang.reflect.InvocationHandler;
導(dǎo)入java.lang.reflect.Method;
導(dǎo)入 java.lang.reflect.Proxy;
導(dǎo)入 java.lang.reflect.UndeclaredThrowableException;
公共最終類 $Proxy0 擴(kuò)展代理實(shí)現(xiàn) Sell {
私有靜態(tài)方法 m1;
私有靜態(tài)方法 m2;
私有靜態(tài)方法 m4;
私有靜態(tài)方法 m3;
私有靜態(tài)方法 m0;
公共 $Proxy0(InvocationHandler var1) 拋出 {
超級(jí)(var1);
}
public final boolean equals(Object var1) throws {
嘗試 {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} 捕捉(運(yùn)行時(shí)異常 | 錯(cuò)誤 var3){
拋出 var3;
} 捕捉(Throwable var4){
拋出新的 UndeclaredThrowableException(var4);
}
}
公共最終字符串 toString() 拋出 {
嘗試 {
return (String)super.h.invoke(this, m2, (Object[])null);
} 捕捉(運(yùn)行時(shí)異常 | 錯(cuò)誤 var2){
拋出 var2;
} 捕捉(Throwable var3){
拋出新的 UndeclaredThrowableException(var3);
}
}
公共最終無效廣告()拋出{
嘗試 {
super.h.invoke(this, m4, (Object[])null);
} 捕捉(運(yùn)行時(shí)異常 | 錯(cuò)誤 var2){
拋出 var2;
} 捕捉(Throwable var3){
拋出新的 UndeclaredThrowableException(var3);
}
}
公共最終無效出售()拋出{
嘗試 {
super.h.invoke(this, m3, (Object[])null);
} 捕捉(運(yùn)行時(shí)異常 | 錯(cuò)誤 var2){
拋出 var2;
} 捕捉(Throwable var3){
拋出新的 UndeclaredThrowableException(var3);
}
}
公共最終 int hashCode() 拋出 {
嘗試 {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} 捕捉(運(yùn)行時(shí)異常 | 錯(cuò)誤 var2){
拋出 var2;
} 捕捉(Throwable var3){
拋出新的 UndeclaredThrowableException(var3);
}
}
靜止的 {
嘗試 {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m4 = Class.forName("com.choupangxia.proxy.Sell").getMethod("ad");
m3 = Class.forName("com.choupangxia.proxy.Sell").getMethod("sell");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} 捕捉(NoSuchMethodException var2){
拋出新的 NoSuchMethodError(var2.getMessage());
} 捕捉(ClassNotFoundException var3){
拋出新的 NoClassDefFoundError(var3.getMessage());
}
}
}
可以看到$Proxy0(代理類)繼承了Proxy類,實(shí)現(xiàn)了所有被代理的接口,以及equals、hashCode、toString等方法。
由于動(dòng)態(tài)代理類繼承了 Proxy 類,因此每個(gè)代理類都與一個(gè) InvocationHandler 方法調(diào)用處理器相關(guān)聯(lián)。
類和所有方法都用public final修飾,所以只能使用代理類,不能再繼承。
每個(gè)方法都有一個(gè) Method 對(duì)象,該對(duì)象在靜態(tài)代碼塊中創(chuàng)建并以“m+number”格式命名。
通過 super.h.invoke(this,m1,(Object[])null); 調(diào)用該方法 而super.h.invoke其實(shí)是一個(gè)LogHandler對(duì)象,在創(chuàng)建代理時(shí)傳遞給Proxy.newProxyInstance。它繼承 InvocationHandler 類并負(fù)責(zé)實(shí)際的調(diào)用處理邏輯。
以上就是關(guān)于“Java代理模式和動(dòng)態(tài)代理詳細(xì)信息”的介紹,如果大家對(duì)此比較感興趣,想了解更多相關(guān)知識(shí),可以關(guān)注一下動(dòng)力節(jié)點(diǎn)的Java設(shè)計(jì)模式,里面有更豐富的知識(shí)等著大家去學(xué)習(xí),希望對(duì)大家能夠有所幫助哦。
相關(guān)閱讀
Java實(shí)驗(yàn)班
0基礎(chǔ) 0學(xué)費(fèi) 15天面授
Java就業(yè)班
有基礎(chǔ) 直達(dá)就業(yè)
Java夜校直播班
業(yè)余時(shí)間 高薪轉(zhuǎn)行
Java在職加薪班
工作1~3年,加薪神器
Java架構(gòu)師班
工作3~5年,晉升架構(gòu)
提交申請(qǐng)后,顧問老師會(huì)電話與您溝通安排學(xué)習(xí)