1. 程式人生 > >代理模式(Proxy Pattern)(二):Java API中的動態代理

代理模式(Proxy Pattern)(二):Java API中的動態代理

一、Java動態代理

        相對於靜態代理的代理類在編譯時生成(.class檔案),動態代理與其的區別是:動態代理類在執行時在JVM中生成。Java 動態代理機制的出現,使得 Java 開發人員不用手工編寫代理類,只要簡單地指定一組介面及委託類(實現了InvocationHandler介面)物件,便能動態地獲得代理類,避免了靜態代理中代理類的急劇膨脹問題。代理類會負責將所有的方法呼叫分派到委託物件上反射執行,在分派執行的過程中,開發人員還可以按需調整委託類物件及其功能,這是一套非常靈活有彈性的代理框架。

二、所涉及到的API中的類

Java動態代理的相關類位於java.lang.reflect包下,一般主要涉及到以下兩個:

(1)InvocationHandler:呼叫處理器介面,該介面中僅定義了一個方法如下:

——public Object invoke(Object proxy, Method method, Object[] args)

在實際使用時,第一個引數proxy一般是指代理類,method是指被代理的方法的Method物件,args為該方法的引數陣列。這個抽象方法在代理類中動態實現。

我們在使用動態代理時要自定義呼叫處理器InvocationHandlerImpl實現該介面,通過對invoke方法的實現處理對被代理物件的方法訪問的控制。InvocationHandlerImpl中包含被代理的物件的引用。


(2)Proxy:輔助生成動態代理類(實際上也是是動態代理類的父類),主要方法有

——protected Proxy(InvocationHandler h)

建構函式,用於給內部的InvocationHandler型別的屬性h賦值。

  • 引數h即我們自定義的呼叫處理器(實現了InvocationHandler介面)的物件。

——public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)

獲得一個代理類(類對應的Class物件)

  • loader指定類的載入器
  • interfaces是代理類要實現的介面
    一般是被代理類所擁有的全部介面的陣列。

—— public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

返回代理類的一個例項。返回後的代理類可以當作被代理類使用。

  • 引數java.lang.ClassLoader:這是類載器類,負責將類的位元組碼裝載到 Java 虛擬機器(JVM)中併為其定義類物件,然後該類才能被使用。Proxy 靜態方法生成動態代理類同樣需要通過類載器來進行載才能使用,它與普通類的唯一區別就是其位元組碼是由 JVM 在執行時動態生成的而非預存在於任何一個 .class 檔案中。每次生成動態代理類物件時都需要指定一個類載器物件。
  • interfaces即代理類要實現的介面,一般是被代理類所實現的全部介面的陣列。
  • h即我們傳入的呼叫處理器物件(實現了InvocationHandler介面)
該方法封裝了前幾個方法,簡化了我們使用動態代理的過程。

——public static boolean isProxyClass(Class<?> cl)

判斷指定的類是否是一個動態代理類。

  • 引數cl即類對應的Class物件

——public static InvocationHandler getInvocationHandler(Object proxy)

獲取指定代理物件所關聯的呼叫處理器。

  • 引數proxy即代理類例項

三、動態代理的過程

1.類之間的關係


2.使用動態代理的步驟(細化):

(1)通過實現 InvocationHandler 介面建立自己的呼叫處理器(持有被代理類例項的引用);

(2)通過為 Proxy 類的靜態方法getProxyClass根據指定的 ClassLoader 物件和一組 interface 來建立動態代理類;

(3)通過反射機制獲得動態代理類的建構函式的Constructor物件,其唯一引數型別是呼叫處理器介面型別;

(4)通過反射機制由建構函式的Constructor物件建立動態代理類的例項,構造時呼叫處理器物件作為引數被傳入(在這之前需建立被代理類及呼叫處理器的例項)。

(5)通過代理物件呼叫方法。

程式碼:

   //步驟1: InvocationHandlerImpl 實現了 InvocationHandler 介面,並能實現方法呼叫從代理類到委託類的分派轉發
   // 其內部通常包含指向委託類(即被代理類)例項的引用,用於真正執行分派轉發過來的方法呼叫
   InvocationHandler handler = new InvocationHandlerImpl(..); //可以將被代理類物件作為引數傳入

   // 步驟2:通過 Proxy 為包括 InterfaceX 介面在內的一組介面動態建立動態代理類
   Class clazz = Proxy.getProxyClass(classLoader, new Class[] { InterfaceX.class, ... }); 

   //步驟3: 通過反射由生成的動態代理類的Class物件獲得其建構函式的Constructor物件
   Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class }); 

   //步驟4: 通過建構函式物件建立動態代理類物件
   InterfaceX proxy = (InterfaceX)constructor.newInstance(new Object[] { handler }); 

   //步驟5:通過代理呼叫方法,此方法即代理方法(假設為request())
   proxy.request();

3.簡化的步驟

Proxy的靜態方法    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,  InvocationHandler h)將步驟2、3、4封裝起來了,我們可以更方便地使用動態代理:

(1)通過實現 InvocationHandler 介面建立自己的呼叫處理器(持有被代理類例項的引用);

(2)通過Proxy類的靜態方法newProxyInstance(...)建立代理類的例項。

(3)通過代理類物件呼叫方法。

程式碼:

      //步驟1: 自定義呼叫處理器類InvocationHandlerImpl(實現 InvocationHandler介面,且持有被代理類的引用)
      InvocationHandler handler = new InvocationHandlerImpl(..); 

      //步驟2: 通過 Proxy 的靜態方法newProxyInstance(..)直接建立動態代理類例項
      InterfaceX proxy = (InterfaceX)Proxy.newProxyInstance( classLoader,interfaces, handler ); 
      
     //步驟3:通過代理呼叫方法,此方法即代理方法(假設為request())
      proxy.request();

說明:上述步驟中省略了一些步驟:抽象介面Subject、被代理類RealSubject類及物件的建立,在動態代理還是靜態代理的使用中都必須這樣做,這裡假設這些類已經寫好了。還有就是一些引數如介面陣列、載入器物件等的取值是根據一些常用的方法得到的(見下面的例子)。

四、動態代理示例

1.抽象角色Subject

public interface Subject
{
	public void request();
}

2.真實角色,即被代理的類RealSubject

public class RealSubject  implements Subject
{
	@Override
	public void request()
	{
		System.out.println("from real subject");
	}

}

3.呼叫處理器類MyInvocationHandler

public class MyInvocationHandler implements InvocationHandler
{
	//持有被代理類的引用
	private Subject real;
	public MyInvocationHandler(Subject real)
	{
		this.real=real;
	}
	public Object invoke(Object proxy, java.lang.reflect.Method method,
			Object[] args) throws Throwable
	{
		System.out.println("before");//可以附加操作控制對真實物件方法的訪問
		//System.out.println(proxy.getClass().getName()+"---"+proxy.getClass().getSuperclass().getName()+"---"+method.getName());
		Object obj=method.invoke(real, args);//執行被代理物件的方法
		System.out.println("after");
        return obj;//obj是method方法返回的資料
	}
}

4.動態代理

public class DynamicTest
{
	public static void main(String[] args)
	{
	  Subject real=new RealSubject();
	  InvocationHandler h=new MyInvocationHandler(real);
	  
	  //獲得被代理類所實現的所有介面的陣列,在這裡陣列中只有Subject.class一個元素
	  Class[] interfaces= real.getClass().getInterfaces();
	  
	  //獲得類載入器
	  ClassLoader loader=h.getClass().getClassLoader();
	  
	  //獲得動態代理類的例項
      Object s=java.lang.reflect.Proxy.newProxyInstance(loader,interfaces, h);
      
      //通過代理類物件呼叫方法
      Subject sub=(Subject)s;
      sub.request();
	}
}

五、關於動態代理類

1.包:

如果所代理的介面都是 public 的,那麼它將被定義在頂層包(即包路徑為空),如果所代理的介面中有非 public 的介面(因為介面不能被定義為 protect 或 private,所以除 public 之外就是預設的 package 訪問級別),那麼它將被定義在該介面所在包(假設代理了 com.pattern.proxy 包中的某非 public 介面 A,那麼新生成的代理類所在的包就是 com.pattern.proxy),這樣設計的目的是為了最大程度的保證動態代理類不會因為包管理的問題而無法被成功定義並訪問;

驗證方式:將Subject的public修飾符去掉,在呼叫處理器MyInvocationHandler的invoke()方法中使用proxy.getClass().getPackage()獲得動態代理類的包進行驗證。

2.類修飾符:

該代理類具有 final 和 public 修飾符,意味著它可以被所有的類訪問,但是不能被再度繼承;

驗證方式:在呼叫處理器MyInvocationHandler的invoke()方法中使用proxy.getClass().getModifiers()獲得類的修飾符欄位值,然後與幫助文件中的常量欄位表對比(或使用Modifier類的相關方法判斷)。

3.類名:

格式是“$ProxyN”,其中 N 是一個逐一遞增的阿拉伯數字,代表 Proxy 類第 N 次生成的動態代理類,值得注意的一點是,並不是每次呼叫 Proxy 的靜態方法建立動態代理類都會使得 N 值增加,原因是如果對同一組介面(包括介面排列的順序相同)試圖重複建立動態代理類,它會很聰明地返回先前已經建立好的代理類的類物件,而不會再嘗試去建立一個全新的代理類,這樣可以節省不必要的程式碼重複生成,提高了代理類的建立效率。

檢視Proxy類的原始碼中關於類名格式的資訊,可以通過相同方式生成兩個動態代理類,列印兩代理類物件的hashCode值判斷是否重複生成代理類

4.動態代理類的繼承關係:

   (1)與Proxy:

         可以看出,Proxy是動態代理類的父類,動態代理類可以呼叫Proxy中的方法。每個動態代理例項都會關聯一個呼叫處理器物件,可以通過 Proxy 提供的靜態方法 getInvocationHandler 去獲得代理類例項的呼叫處理器物件。

   (2)介面組的限制:

  • 動態代理類最多實現65535個介面,這是由JVM限制的,在Proxy的原始碼中可以很容易發現,當介面數超過65535時,就會throw new IllegalArgumentException("interface limit exceeded");
  • 要注意不能有重複的介面,以避免動態代理類程式碼生成時的編譯錯誤。(見原始碼)
  • 這些介面對於類裝載器必須可見,否則類裝載器將無法連結它們,將會導致類定義失敗。(使用Class.forName()進行可見性判斷,見原始碼)
  • 需被代理的所有非 public 的介面必須在同一個包中,否則代理類生成也會失敗。(在上面的包的說明中已經能證實到,因為一個類只能位於一個包下)

5.代理的方法:

因為動態代理類$ProxyN是Proxy的子類,Proxy又繼承於Object,所以$ProxyN可以呼叫Proxy、Object、它所實現的所有介面中的方法。

(1)使用動態代理類的例項呼叫Proxy中的方法時,不會對這些方法(Proxy的這些方法都是static的)進行代理(即不會委託到呼叫處理器的invoke上處理)。

(2)使用動態代理類的例項呼叫從Object類繼承下來的方法時,會對toString()、equals()以及hashCode()這三個方法進行代理(委託到invoke()方法上反射執行),對於其他方法則不代理。

(3)使用動態代理類的例項呼叫從介面(建立代理例項時傳入的介面陣列)中實現的方法時,這些方法都會被代理,動態代理有意義的地方就在於此。但是需要注意的是當代理的一組介面有重複宣告的方法且該方法被呼叫時,代理類總是從排在最前面的介面中獲取方法物件並分派給呼叫處理器,而無論代理類例項是否正在以該介面(或繼承於該介面的某子介面)的形式被外部引用,因為在代理類內部無法區分其當前的被引用型別。

例如:介面陣列      Class[] interfaces= new Class[]{Jia.class,Subject.class,};,並且這兩個介面中都聲明瞭同樣的方法request(),那麼當 Subject sub=(Subject)s;sub.request();時並不是呼叫的Subject中宣告的方法,而是Jia中宣告的方法(雖然當前的引用型別是Subject,但是Jia的順序在Subject的前面)。

6.異常處理:

我們必須遵守一個繼承原則是:即子類覆蓋父類或實現父介面的方法時,丟擲的異常必須在原方法支援的異常列表之內。同樣在此處,動態代理類丟擲的異常也要在其父類或父介面方法支援的異常列表中。但是如果invoke()方法丟擲的異常不在代理類的父類或父介面方法支援的異常列表中,那麼將會丟擲 UndeclaredThrowableException 異常。這個異常是一個 RuntimeException 型別,所以不會引起編譯錯誤。通過該異常的 getCause 方法,還可以獲得原來那個不受支援的異常物件,以便於錯誤診斷。

驗證方式:在上例中的invoke()方法中丟擲異常:throw new ClassNotFoundException();

六、不足

Proxy僅支援interface的代理,這是由java單繼承的特點所造成的,但是Proxy的設計已經非常完美了,不完美並不等於不偉大。

另外,cglib也可以實現類的代理。

(轉載請註明出處:http://blog.csdn.net/jialinqiang/article/details/8950989)