1 概述
代理模式(Proxy)就是為一個對象創建一個替身,用來控制對當前對象的訪問。目的就是為了在不直接操作對象的前提下對對象進行訪問。
根據代理類和被代理類的關系來區分的話,可以分為靜態代理和動態代理。
(1)靜態代理:在運行之前,就確定好代理類、被代理類之間的關系。
(2)動態代理:在運行時動態的創建一個代理類,實現一個或多個接口,將方法的調用轉發到指定的類。
根據不同的功用性,可以分為遠程代理、虛擬代理、保護代理、緩存代理、寫入代理等。
(1)遠程代理:為一個位於不同的JVM堆中的對象提供一個本地的代理對象,Java有自帶的RMI方式來實現;
(2)虛擬代理:為創建開銷大的對象提供代理,當對象在創建前和創建中時,由虛擬代理來作為對象的替身,對象創建后,代理就會將請求直接轉給對象。
(3)保護代理: 主要用於當對象應該有不同的訪問權限的時。
2 示例
2.1 靜態代理
在智能手機如此火熱的今天,動手寫個手機遙控關閉電腦的小軟件也不是難事,今天就以它作為例子。手機代理電腦關機鍵,完成關機功能。
首先來個關電腦的接口:
1 package org.scott.proxy; 2 /** 3 * @author Scott 4 * @date 2013-11-27 5 * @description 6 */ 7 public interface Operate { 8 public void powerOff(); 9 }
然后是實際關電腦的執行類:
1 package org.scott.proxy; 2 /** 3 * @author Scott 4 * @date 2013-11-27 5 * @description 6 */ 7 public class PCPowerOff implements Operate{ 8 9 @Override 10 public void powerOff() { 11 System.out.println("The pc is power off."); 12 } 13 }
現在就利用靜態代理模式,來創建個手機關閉電腦的代理類:
1 package org.scott.proxystatic; 2 3 import org.scott.proxy.Operate; 4 import org.scott.proxy.PCPowerOff; 5 6 /** 7 * @author Scott 8 * @date 2013-11-27 9 * @description 10 */ 11 public class PhoneProxy implements Operate{ 12 13 private PCPowerOff pcPowerOff; 14 public PhoneProxy(){ 15 this.pcPowerOff = new PCPowerOff(); 16 } 17 18 @Override 19 public void powerOff() { 20 prePowerOff(); 21 this.pcPowerOff.powerOff(); 22 afterPowerOff(); 23 } 24 25 public void prePowerOff(){ 26 System.out.println("Before power off the computer by static proxy, check the email list."); 27 } 28 29 public void afterPowerOff(){ 30 System.out.println("After power off the computer by static proxy, say good-night."); 31 } 32 }
來個測試類:
1 package org.scott.proxystatic; 2 3 import org.scott.proxy.Operate; 4 5 /** 6 * @author Scott 7 * @date 2013-11-27 8 * @description 9 */ 10 public class ProxyStaticTest { 11 12 public static void main(String[] args) { 13 Operate operate = new PhoneProxy(); 14 operate.powerOff(); 15 } 16 17 }
執行結果:
Before power off the computer by static proxy, check the email list. The pc is power off. After power off the computer by static proxy, say good-night.
靜態代理有兩個弱點(源自網絡總結):
1)代理對象的一個接口只服務於一種類型的對象,如果要代理的方法很多,勢必要為每一種方法都進行代理,靜態代理在程序規模稍大時就無法勝任了。
2)如果接口增加一個方法,除了所有實現類需要實現這個方法外,所有代理類也需要實現此方法。增加了代碼維護的復雜度。
於是乎,到了“動態代理”上場的時候了。
2.2 動態代理
目前,實現動態代理,主要是靠反射,並依賴於JDK中的java.lang.reflect.Proxy類、java.lang.ClassLoader類以及java.lang.reflect.InvocationHandler接口。
2.2.1 java.lang.reflect.Proxy
這個類提供了一組靜態方法來為一組接口動態地通過反射生成代理類及其對象。
常用的方法有以下四個:
//用於獲取指定代理對象所關聯的調用處理器 static InvocationHandler getInvocationHandler(Object proxy) //用於獲取關聯於指定類裝載器和一組接口的動態代理類的類對象 static Class getProxyClass(ClassLoader loader, Class[] interfaces) //用於判斷指定類對象是否是一個動態代理類 static boolean isProxyClass(Class cl) //用於為指定類裝載器、一組接口及調用處理器生成動態代理類實例 static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
2.2.2 java.lang.ClassLoader
類裝載器類負責加載類,將類的字節碼裝載到JVM中並為其定義類對象。Proxy靜態方法生成動態代理類同樣需要通過類裝載器來進行裝載才能使用,並且字節碼是在程序運行過程中動態產生的。
2.2.3 java.lang.reflect.InvocationHandler
InvocationHandler實現了代理的行為。每個代理實例都具有一個關聯的調用處理程序,對代理實例調用方法時,將對方法調用進行編碼並將其指派到它的調用處理程序的 invoke
方法。每次生成動態代理類對象時都要指定一個對應的調用處理器對象。
Object invoke(Object proxy, Method method, Object[] args) throws Throwable
參數:
(1)proxy:在其上調用方法的代理對象;
(2)method:對應於在代理實例上調用的接口方法的 Method 實例。Method 對象的聲明類將是在其中聲明方法的接口,該接口可以是代理類賴以繼承方法的代理接口的超接口;
(3)args:包含傳入代理實例上方法調用的參數值的對象數組,如果接口方法不使用參數,則為 null。基本類型的參數被包裝在適當基本包裝器類(如 java.lang.Integer 或 java.lang.Boolean)的實例中。
返回:
從代理實例的方法調用返回的值。
如果接口方法的聲明返回類型是基本類型,則此方法返回的值一定是相應基本包裝對象類的實例;否則,它一定是可分配到聲明返回類型的類型。如果此方法返回的值為null並且接口方法的返回類型是基本類型,則代理實例上的方法調用將拋出 NullPointerException。否則,如果此方法返回的值與上述接口方法的聲明返回類型不兼容,則代理實例上的方法調用將拋出 ClassCastException。
2.2.4 動態代理實現步驟
不要被上面列出的三個怪東西搞暈了,下面列一列由牛人們給出的實現動態代理模式的步驟:
(1)實現InvocationHandler接口,創建自己的調用處理器
(2)給Proxy類提供ClassLoader和代理接口類型數組創建動態代理類
(3)以調用處理器類型為參數,利用反射機制得到動態代理類的構造函數
(4)以調用處理器對象為參數,利用動態代理類的構造函數創建動態代理類對象
2.2.5 Code
好吧,來段代碼來說明上面一連串的文字描述,例子還是上面的利用手機遠程關閉電腦。
還是利用那個Operate接口和被代理類PCPowerOff。下面才是動態代理所特有的。
自定義的Invocation處理類:
1 package org.scott.proxydynamic; 2 3 import java.lang.reflect.InvocationHandler; 4 import java.lang.reflect.Method; 5 6 /** 7 * @author Scott 8 * @date 2013-11-27 9 * @description 10 */ 11 public class MyInvocationHandler implements InvocationHandler { 12 private Object obj; 13 14 public MyInvocationHandler(Object obj){ 15 this.obj = obj; 16 } 17 18 @Override 19 public Object invoke(Object proxy, Method method, Object[] args) 20 throws Throwable { 21 prePowerOff(); 22 23 //通過反射獲取 24 method.invoke(obj, args); 25 26 afterPowerOff(); 27 28 return null; 29 } 30 31 public void prePowerOff(){ 32 System.out.println("Before power off the computer by dynamic proxy, check the email list."); 33 } 34 35 public void afterPowerOff(){ 36 System.out.println("After power off the computer by dynamic proxy, say good-night."); 37 } 38 }
客戶端也很重要,再寫個測試類:
1 package org.scott.proxydynamic; 2 3 import java.lang.reflect.InvocationHandler; 4 import java.lang.reflect.Proxy; 5 6 import org.scott.proxy.Operate; 7 import org.scott.proxy.PCPowerOff; 8 9 /** 10 * @author Scott 11 * @date 2013-11-27 12 * @description 13 */ 14 public class ProxyDynamicTest { 15 16 @SuppressWarnings("rawtypes") 17 public static void main(String[] args) { 18 Operate pcPowerOff = new PCPowerOff(); 19 InvocationHandler invocation = new MyInvocationHandler(pcPowerOff); 20 Class clazz = pcPowerOff.getClass(); 21 22 Operate proxy = (Operate) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), invocation); 23 proxy.powerOff(); 24 } 25 26 }
運行結果:
Before power off the computer by dynamic proxy, check the email list.
The pc is power off.
After power off the computer by dynamic proxy, say good-night.
3 小結
是不是覺得代理模式,特別是靜態代理那個例子和適配器有點類似?來比較一下。
(1)和適配器模式Adapter比較
適配器Adapter 為它所適配的對象提供了一個不同的接口。相反,代理提供了與它的被代理實體相同的接口。然而,用於訪問保護的代理可能會拒絕執行實體會執行的操作,因此,它的接口實際上可能只是實體接口的一個子集。
(2)和裝飾器模式Decorator比較
盡管Decorator的實現部分與代理相似,但 Decorator的目的不一樣。Decorator為對象添加一個或多個功能,而代理則控制對對象的訪問。
代理模式,尤其是動態代理模式,應用范圍非常廣泛,最典型的例子就是HDFS中的RPC實現,利用動態代理加上Protobuf,實現NameNode之間、NameNode和DataNode之間、NameNode和客戶端之間、DataNode和DataNode之間、DataNode和客戶端之間的通信。之前寫了個HDFS的RPC分析,請見: