這里總結下java中的靜態代理和動態代理。
Java中有一個設計模式是代理模式
代理模式是常用的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()方法中統一處理,就可以給所有被代理的方法進行統一添加相同的操作了。例如這里的方法計時,所有的被代理對象執行的方法都會被計時。
動態代理的過程,代理對象和被代理對象的關系不像靜態代理那樣一目了然,清晰明了。因為動態代理的過程中,我們並沒有實際看到代理類,也沒有很清晰地看到動態代理中被代理對象是怎么被代理的,也不知道為什么代理對象執行的方法都會通過InvocationHandler中的invoke()方法執行。為了弄清楚這些問題,就需要分析源碼碼,下次再用另外的篇幅來分析好了。
"暗戀或單戀都是人生中一場漫長的走神。"