更新時間:2022-12-16 10:14:05 來源:動力節(jié)點(diǎn) 瀏覽1290次
代理模式是常用的Java設(shè)計模式,特征是代理類與委托類有相同的接口,代理類主要負(fù)責(zé)為委托類預(yù)處理消息、過濾消息、把消息轉(zhuǎn)發(fā)給委托類,以及事后處理消息等。
代理類與委托類之間通常會存在關(guān)聯(lián)關(guān)系,一個代理類的對象與一個委托類的對象關(guān)聯(lián),代理類的對象本身并不真正實(shí)現(xiàn)服務(wù),而是通過調(diào)用委托類的對象的相關(guān)方法,來提供特定的服務(wù)。簡單的說就是,我們在訪問實(shí)際對象的時候,是通過代理對象來訪問的,代理模式就是在訪問實(shí)際對象的時候引入一定程度的間接性,因?yàn)檫@種間接性,可以附加多種用途。
靜態(tài)代理,由程序員創(chuàng)建或特定工具自動生成源代碼,在編譯時已經(jīng)將接口,被代理類(委托類),代理類等確定下來。在程序運(yùn)行之前,代理類的.class文件就已經(jīng)生成。
假定一個團(tuán)購長途汽車車票的場景,有50個乘客要去客運(yùn)站買長途汽車車票,由跟車人去代買50張車票。在這里,乘客有買車票的行為,跟車人也有買車票的行為,那么乘客買車票就可以由跟車人去代理執(zhí)行。
首先是創(chuàng)建一個買票人的接口。
/**
* 買票人接口
*/
public interface TickectBuyer {
// 買車票
void buyTicket();
}
然后是創(chuàng)建一個乘客類(委托類),去實(shí)現(xiàn)買票人接口。
/**
* 乘客類,實(shí)現(xiàn)了買票人接口
*/
public class Passenger implements TickectBuyer {
private String name;
Passenger(String name) {
this.name = name;
}
@Override
public void buyTicket() {
System.out.println("乘客【" + name + "】買了一張車票。");
}
}
然后是創(chuàng)建一個乘客代理類,同樣實(shí)現(xiàn)買票人接口。
因?yàn)槌钟幸粋€乘客類對象,所以它可以代理乘客類對象執(zhí)行買車票的行為。
/**
* 乘客代理類,也實(shí)現(xiàn)買票人接口
*/
public class PassengerProxy implements TickectBuyer {
private String name;
// 被代理的乘客類
private Passenger passenger;
PassengerProxy(String name, Passenger passenger) {
this.name = name;
// 只代理乘客類
if (passenger.getClass() == Passenger.class) {
this.passenger = passenger;
}
}
@Override
public void buyTicket() {
// 委托類附加的操作
System.out.print("代買人【" + name + "】代");
// 調(diào)用委托類(乘客類)的方法
passenger.buyTicket();
}
}
最后創(chuàng)建一個測試類測試代理的結(jié)果。
/**
* 乘客代理測試類
*/
public class PassengerProxyTest {
public static void main(String[] args) {
// 乘客陳小雞(乘客類)
Passenger passenger = new Passenger("陳小雞");
// 跟車人(乘客代理類)
PassengerProxy carFollower = new PassengerProxy("王小狗", passenger);
// 由跟車人代理陳小雞買車票
carFollower.buyTicket();
}
}
結(jié)果是:代買人【王小狗】代乘客【陳小雞】買了一張車票。
這里可以看到,代理類可以通過持有委托類對象去調(diào)用委托類的方法,從而達(dá)到代理委托類去執(zhí)行委托類行為的目的。然后,在調(diào)用委托類方法的時候,可以在調(diào)用的前面或者后面添加代理類自己的行為,比如上面代碼中添加打印代理人信息的行為。這個就是代理模式的一個很大的優(yōu)點(diǎn),可以在代理點(diǎn)切入一些特定的、附加的操作,卻不會改變原來委托要執(zhí)行的行為。
代理類在程序運(yùn)行時常見的代理方式被稱為動態(tài)代理。我們上面靜態(tài)代理的例子中,代理類PassengerProxy是自己定義好的,在程序運(yùn)行之前就已經(jīng)編譯完成。不同的是,動態(tài)代理的代理類并不是在Java代碼中定義好的,而是在運(yùn)行時根據(jù)我們在Java代碼中的指示動態(tài)生成的。相比于靜態(tài)代理,動態(tài)代理的優(yōu)勢在于可以很方便地對代理類的函數(shù)進(jìn)行統(tǒng)一的處理,而不用修改每個代理類中的方法。比如說,想要在每個代理的方法前都加上一個處理方法:
public void buyTicket() {
// 在調(diào)用委托類的方法前,加入其他邏輯處理
beforeMethod();
// 調(diào)用委托類(乘客類)的方法
passenger.buyTicket();
}
這里只有一個buyTickect()方法,就只要寫一次beforeMethod()方法。可是如果有很多個地方都要調(diào)用beforeMethod()方法,就需要改很多個地方,給修改或維護(hù)帶來麻煩。動態(tài)代理就是為了解決這樣的麻煩,而由聰明絕頂(滑稽)的人才想出來的解決方法。
在Java的java.lang.reflect包(反射包啦,看到這應(yīng)該明白動態(tài)代理是用Java的反射機(jī)制實(shí)現(xiàn)的了吧,不會反射的還不去先學(xué)一下反射)下提供了一個Proxy類和InvocationHandler接口,通過這個類和這個接口可以生成JDK動態(tài)代理類和動態(tài)代理對象。
創(chuàng)建一個InvocationHandler對象。
// 創(chuàng)建一個與代理對象相關(guān)聯(lián)的InvocationHandler
InvocationHandler passengerHandler = new MyInvocationHandler<TickertBuyer>(passenger);
使用Proxy類的getProxyClass靜態(tài)方法生成一個動態(tài)代理類passengerProxyClass。
Class<?> passengerProxyClass = Proxy.getProxyClass(TickectBuyer.class.getClassLoader(), new Class<?>[] {TickectBuyer.class});
獲得passengerProxy中一個帶InvocationHandler參數(shù)的構(gòu)造器constructor。
Constructor<?> constructor = passengerProxy.getConstructor(InvocationHandler.class);
通過構(gòu)造器constructor來創(chuàng)建一個動態(tài)實(shí)例passengerProxy。
TickectBuyer passengerProxy = (TickectBuyer) constructor.newInstance(passengerHandler);
這樣,一個動態(tài)代理對象passengerProxy就創(chuàng)建完畢了。另外的,上面四個步驟可以通過Proxy類的newProxyInstancs方法來簡化:
// 創(chuàng)建一個與代理對象相關(guān)聯(lián)的InvocationHandler
InvocationHandler passengerHandler = new MyInvocationHandler<TickectBuyer>(passenger);
// 創(chuàng)建一個代理對象passengerProxy,代理對象的每個執(zhí)行方法都會替換執(zhí)行Invocation中的invoke方法
TickectBuyer passengerProxy= (TickectBuyer) Proxy.newProxyInstance(TickectBuyer.class.getClassLoader(), new Class<?>[]{TickectBuyer.class}, passengerHandler);
那么動態(tài)代理是要如何執(zhí)行,如何通過代理對象來執(zhí)行被代理對象的方法呢。我們可以通過完整的動態(tài)代理的例子來說明。還是上面跟車人幫乘客代買車票的例子。
首先是定義一個TickectBuyer接口,其中有一個未實(shí)現(xiàn)的buyTickect()方法。
public interface TickectBuyer {
// 買車票
void buyTicket();
}
然后是創(chuàng)建需要被代理的乘客類。
public class Passenger implements TickectBuyer {
private String name;
Passenger(String name) {
this.name = name;
}
@Override
public void buyTicket() {
try {
// 假設(shè)買一張票要5秒
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("乘客【" + name + "】買了一張車票。");
}
}
然后定義一個檢測方法執(zhí)行時間的工具類,在任何方法執(zhí)行之前先調(diào)用start()方法,執(zhí)行后調(diào)用finish()方法,就可以計算出該方法的運(yùn)行時間,這也是一個最簡單的方法執(zhí)行時間檢測工具。
public class MonitorUtil
private static ThreadLocal<Long> tl = new ThreadLocal<>();
public static void start() {
tl.set(System.currentTimeMillis());
}
// 結(jié)束時打印耗時
public static void finish(String methodName) {
long finishTime = System.currentTimeMillis();
System.out.println(methodName + "方法耗時" + (finishTime - tl.get()) + "ms");
}
}
然后創(chuàng)建PassengerInvocationHandler類,實(shí)現(xiàn)InvocationHandler接口。這個類中持有一個被代理對象的實(shí)例target。InvocationHandler中有一個invoke()方法,所有執(zhí)行代理對象的方法都會被替換成執(zhí)行invoke()方法。在invoke()方法中執(zhí)行被代理對象target的相應(yīng)方法。當(dāng)然,在代理過程中,我們可以在真正執(zhí)行被代理對象的方法前加入自己的其他處理。這也是Spring中AOP實(shí)現(xiàn)的主要原理,其實(shí)就是Java基礎(chǔ)的反射機(jī)制,沒有什么神秘的黑科技啦。
public class PassengerInvocationHandler<T> implements InvocationHandler {
// InvocationHandler持有的被代理對象
private T target;
public PassengerInvocationHandler(T target) {
this.target = target;
}
/**
* proxy:代表動態(tài)代理對象
* method:代表正在執(zhí)行的方法
* args:代表調(diào)用目標(biāo)方法時傳入的實(shí)參
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理執(zhí)行" + method.getName() + "方法");
// 代理過程中插入監(jiān)測方法,計算該方法耗時
MonitorUtil.start();
Object result = method.invoke(target, args);
MonitorUtil.finish(method.getName());
return result;
}
}
做完上面的工作后,我們就可以具體來創(chuàng)建動態(tài)代理對象了。
public class PassengerProxyTest2 {
public static void main(String[] args) {
// 創(chuàng)建一個實(shí)例對象,這個對象是被代理的對象
TickectBuyer zhangsan = new Passenger("張三");
// 創(chuàng)建一個與代理對象相關(guān)聯(lián)的InvocationHandler
InvocationHandler passengerHandler = new PassengerInvocationHandler<TickectBuyer>(zhangsan);
// 創(chuàng)建一個代理對象passengerProxy來代理zhangsan,代理對象的每個執(zhí)行方法都會替換執(zhí)行Invocation中的invoke方法
TickectBuyer passengerProxy = (TickectBuyer) Proxy.newProxyInstance(TickectBuyer.class.getClassLoader(),
new Class<?>[]{TickectBuyer.class}, passengerHandler);
// 代理執(zhí)行買車票的方法
passengerProxy.buyTicket();
}
}
我們執(zhí)行這個PassengerProxyTest2類之前,先想以下,我們創(chuàng)建了一個需要被代理的乘客張三,將張三對象傳給了passengerHandler,在創(chuàng)建代理對象passengerProxy時,將passengerHandler作為參數(shù),上面有說到所有執(zhí)行代理對象的方法都會被替換成執(zhí)行invoke()方法,也就是說,最后執(zhí)行的是passengerInvocationHandler中的invoke()方法。

上面說到,動態(tài)代理的優(yōu)勢在于可以很方便地對代理類的方法進(jìn)行統(tǒng)一的處理,而不用修改每個代理類中的方法,這是因?yàn)樗斜淮韴?zhí)行的方法,都是通過InvocationHandler中的invoke()方法調(diào)用的,我們只要在invoke()方法中統(tǒng)一處理,就可以給所有被代理的方法進(jìn)行統(tǒng)一添加相同的操作了。例如這里的方法計時,所有的被代理對象執(zhí)行的方法都會被計時。
Java動態(tài)代理模式的過程,代理對象和被代理對象的關(guān)系不像靜態(tài)代理那樣一目了然,清晰明了。因?yàn)閯討B(tài)代理的過程中,我們并沒有實(shí)際看到代理類,也沒有很清晰地看到動態(tài)代理中被代理對象是怎么被代理的,也不知道為什么代理對象執(zhí)行的方法都會通過InvocationHandler中的invoke()方法執(zhí)行。

初級 202925

初級 203221

初級 202629

初級 203743