- 代理模式
- 代理模式角色定義
- 靜態(tài)代理
3.1 靜態(tài)代理實例
3.2 靜態(tài)代理的缺點- 動態(tài)代理
4.1 基于JDK原生動態(tài)代理實現(xiàn)
1. 代理模式
為其他對象提供一種代理以控制對這個對象的訪問。在某些情況下冬殃,一個對象不適合或者不能直接引用另一個對象,而代理對象可以在客戶端和目標(biāo)對象之間起到中介的作用。
通過代理模式可以做到:
- 隱藏委托類的具體實現(xiàn)。
- 實現(xiàn)客戶與委托類的解耦何陆,在不改變委托類代碼的情況下添加一些額外的功能(日志此迅、權(quán)限)等。
2. 代理模式角色定義
三類對象:
- Subject(抽象主題角色):定義代理類和真實主題的公共對外方法戈二,也是代理類代理真實主題的方法。
- RealSubject(真實主題角色):真正實現(xiàn)業(yè)務(wù)邏輯的類喳资。
- Proxy(代理主題角色):用來代理和封裝真實主題觉吭。
3. 靜態(tài)代理
靜態(tài)代理是指代理類在程序運行前就已經(jīng)存在。
3.1 靜態(tài)代理實例
首先定義一組接口Sell仆邓,用來提供廣告和銷售等功能鲜滩。然后提供Vendor類(廠商,被代理對象)和Shop(超市节值,代理類)徙硅,它們分別實現(xiàn)了Sell接口。
Sell接口定義如下:
package javaBasic.proxy;
/**
* 首先定義一組接口Sell察署,用來提供廣告和銷售等功能闷游。
*/
public interface Sell {
/**
* 出售
*/
void sell();
/**
* 廣告
*/
void ad();
}
Vendor類定義如下:
package javaBasic.proxy;
public class Vendor implements Sell {
@Override
public void sell() {
System.out.println("Shop sell goods");
}
@Override
public void ad() {
System.out.println("Shop advert goods");
}
}
Shop類定義如下:
package javaBasic.proxy;
/**
* 其中代理類Shop通過聚合的方式持有了被代理類Vendor的引用,
* 并在對應(yīng)的方法中調(diào)用Vendor對應(yīng)的方法贴汪。
* 在Shop類中我們可以新增一些額外的處理脐往,比如篩選購買用戶、記錄日志等操作扳埂。
*/
public class Shop implements Sell{
private Sell sell;
public Shop(Sell sell){
this.sell = sell;
}
@Override
public void sell() {
System.out.println("代理類Shop, 處理sell");
sell.sell();
}
@Override
public void ad() {
System.out.println("代理類Shop, 處理ad");
sell.ad();
}
}
其中代理類Shop通過聚合的方式持有了被代理類Vendor的引用业簿,并在對應(yīng)的方法中調(diào)用Vendor對應(yīng)的方法。在Shop類中我們可以新增一些額外的處理阳懂,比如篩選購買用戶梅尤、記錄日志等操作柜思。
客戶端使用代理類的方式:
package javaBasic.proxy;
/**
* 針對客戶看到的是Sell接口提供了功能,而功能又是由Shop提供的巷燥。
* 我們可以在Shop中修改或新增一些內(nèi)容赡盘,而不影響被代理類Vendor。
*/
public class StaticProxy {
public static void main(String[] args) {
// 供應(yīng)商---被代理類
Vendor vendor = new Vendor();
// 創(chuàng)建供應(yīng)商的代理類Shop
Sell sell = new Shop(vendor);
// 客戶端使用時面向的是代理類Shop缰揪。
sell.ad();
sell.sell();
}
}
在上述代碼中陨享,針對客戶看到的是Sell接口提供了功能,而功能又是由Shop提供的钝腺。我們可以在Shop中修改或新增一些內(nèi)容抛姑,而不影響被代理類Vendor。
3.2 靜態(tài)代理的缺點
- 當(dāng)需要代理多個類時艳狐,代理對象要實現(xiàn)與目標(biāo)對象一致的接口定硝。要么,只維護一個代理類來實現(xiàn)多個接口毫目,但這樣會導(dǎo)致代理類過于龐大蔬啡。要么,新建多個代理類蒜茴,但這樣會產(chǎn)生過多的代理類星爪。
- 當(dāng)接口需要增加、刪除粉私、修改方法時,目標(biāo)對象與代理類都要同時修改近零,不易維護诺核。
4. 動態(tài)代理
動態(tài)代理是指代理類在程序運行時進行創(chuàng)建的代理方式。這種情況下久信,代理類并不是在Java代碼中定義的窖杀,而是在運行時根據(jù)Java代碼中的“指示”動態(tài)生成的。
相比于靜態(tài)代理裙士,動態(tài)代理的優(yōu)勢在于可以很方便的對代理類的函數(shù)進行統(tǒng)一的處理入客,而不用修改每個代理類的函數(shù)。
4.1 基于JDK原生動態(tài)代理實現(xiàn)
實現(xiàn)動態(tài)代理通常有兩種方式:JDK原生動態(tài)代理和CGLIB動態(tài)代理腿椎。這里桌硫,我們以JDK原生動態(tài)代理為例。
JDK動態(tài)代理主要涉及兩個類:java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler啃炸。
InvocationHandler接口定義了如下方法:
/**
* 調(diào)用處理程序
*/
public interface InvocationHandler {
Object invoke(Object proxy, Method method, Object[] args);
}
實現(xiàn)了該接口的中介類用做“調(diào)用處理器”铆隘,當(dāng)調(diào)用代理類對象的方法時,這個調(diào)用會直接跳轉(zhuǎn)到invoke方法之中南用,代理類對象作為proxy參數(shù)傳入膀钠,參數(shù)method標(biāo)識了具體調(diào)用的是代理類的哪個方法掏湾,args為該方法的參數(shù)。這樣對代理類中的所有方法的調(diào)用都會變?yōu)閷nvoke的調(diào)用肿嘲,可以在invoke方法中添加統(tǒng)一的處理邏輯(也可以根據(jù)method參數(shù)對不同的代理類方法做不同的處理)融击。
Proxy類用來實例化指定代理對象所關(guān)聯(lián)的調(diào)用處理器。
下面以添加日志為例演示動態(tài)代理雳窟。
首先是建立LogHandler類:
package javaBasic.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Date;
public class LogHandler implements InvocationHandler {
Object target; //被代理的對象, 實際的方法執(zhí)行者
public LogHandler(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(target,args);
after();
return result;
}
//調(diào)用invoke方法之前執(zhí)行
private void before() {
System.out.println(String.format("log start time [%s] ", new Date()));
}
//調(diào)用invoke方法之后執(zhí)行
private void after() {
System.out.println(String.format("log end time [%s] ", new Date()));
}
}
客戶端編寫程序使用動態(tài)代理代碼如下:
package javaBasic.proxy;
import java.lang.reflect.Proxy;
public class DynamicProxyMain {
public static void main(String[] args) {
//創(chuàng)建中介類實例
LogHandler logHandler = new LogHandler(new Vendor());
//獲取代理類實例
Sell sell = (Sell) Proxy.newProxyInstance(Sell.class.getClassLoader(), new Class[]{Sell.class}, logHandler);
//通過代理類對象調(diào)用代理類方法尊浪,實際上會轉(zhuǎn)到invoke方法(LogHandler之中的sell方法)調(diào)用
//這個代理類之中的方法調(diào)用是通過反射實現(xiàn)的
sell.sell();
sell.ad();
}
}
執(zhí)行后結(jié)果(已經(jīng)成功為我們的被代理類統(tǒng)一添加了執(zhí)行方法之前和執(zhí)行方法之后的日志):
log start time [Fri Aug 21 09:39:04 AEST 2020]
Shop sell goods
log end time [Fri Aug 21 09:39:04 AEST 2020]
log start time [Fri Aug 21 09:39:04 AEST 2020]
Shop advert goods
log end time [Fri Aug 21 09:39:04 AEST 2020]
動態(tài)代理的原理:
執(zhí)行上述方法后會生成一個$Proxy0.class類文件。該類繼承了Proxy類涩拙,并且實現(xiàn)了被代理的所有接口际长,以及equals、hashCode兴泥、toString等方法工育。
由于動態(tài)代理類繼承了Proxy類,所以每個代理類都會關(guān)聯(lián)一個InvocationHandler方法調(diào)用處理器搓彻。
類和所有方法都被public final修飾如绸,所以代理類只可被使用,不可以再被繼承旭贬。
每個方法都有一個Method對象來描述怔接,Method對象在static靜態(tài)代碼塊中創(chuàng)建,以“m+數(shù)字”的格式命名稀轨。
調(diào)用方法的時候通過super.h.invoke(this,m1,(Object[])null);調(diào)用扼脐。其中的super.h.invoke實際上是在創(chuàng)建代理的時候傳遞給Proxy.newProxyInstance的LogHandler對象,它繼承InvocationHandler類奋刽,負(fù)責(zé)實際的調(diào)用處理邏輯瓦侮。