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