Java動態代理探討

代理模式:html

  代理模式是經常使用的java設計模式,他的特徵是代理類與委託類有一樣的接口,代理類主要負責爲委託類預處理消息、過濾消息、把消息轉發給委託類,以及過後處理消息等。經過代理模式,能夠延遲建立對象,限制訪問某個對象,也就是說,提供一組方法給普通用戶,特別方法給管理員用戶。java

UML圖:git

簡單結構示意圖:程序員

  爲了保持行爲的一致性,代理類和委託類一般會實現相同的接口,因此在訪問者看來二者沒有絲毫的區別。github

按照代理的建立時期,代理類能夠分爲兩種:設計模式

  • 靜態代理:由程序員建立或特定工具自動生成源代碼,再對其編譯。在程序運行前,代理類的.class文件就已經存在了。 
  • 動態代理:在程序運行時,運用反射機制動態建立而成。 

靜態代理:dom

  爲了幫助理解代理模式,來看一下靜態代理的示例代碼(代碼摘自裏):ide

Count.java 函數

 1 /** 
 2  * 定義一個帳戶接口 
 3  * @author Administrator 
 4  */  
 5 public interface Count {  
 6     // 查看帳戶方法  
 7     public void queryCount();  
 8     // 修改帳戶方法  
 9     public void updateCount();  
10 }  

CountImpl.java 工具

 1 /** 
 2  * 委託類(包含業務邏輯) 
 3  * @author Administrator 
 4  */  
 5 public class CountImpl implements Count {  
 6   
 7     @Override  
 8     public void queryCount() {  
 9         System.out.println("查看帳戶方法...");  
10     }  
11   
12     @Override  
13     public void updateCount() {  
14         System.out.println("修改帳戶方法...");  
15     }  
16 }

CountProxy.java

 1 public class CountProxy implements Count {  
 2     private CountImpl countImpl;  
 3     /** 
 4      * 覆蓋默認構造器 
 5      * @param countImpl 
 6      */  
 7     public CountProxy(CountImpl countImpl) {  
 8         this.countImpl = countImpl;  
 9     }  
10   
11     @Override  
12     public void queryCount() {  
13         System.out.println("事務處理以前");  
14         // 調用委託類的方法;  
15         countImpl.queryCount();  
16         System.out.println("事務處理以後");  
17     }  
18   
19     @Override  
20     public void updateCount() {  
21         System.out.println("事務處理以前");  
22         // 調用委託類的方法;  
23         countImpl.updateCount();  
24         System.out.println("事務處理以後");  
25     }  
26 }  

TestCount.java 

 1 /** 
 2  *測試Count類 
 3  * @author Administrator 
 4  */  
 5 public class TestCount {  
 6     public static void main(String[] args) {  
 7         CountImpl countImpl = new CountImpl();  
 8         CountProxy countProxy = new CountProxy(countImpl);  
 9         countProxy.updateCount();  
10         countProxy.queryCount();  
11     }  
12 }  

  以上靜態代理的代碼結合前面的結構圖和UML圖,相信不難理解代理模式的基本原理。

JDK動態代理

  爲了提升代理的靈活性和可擴展性,減小重複代碼,咱們可使用JDK提供的動態代理。

實現JDK動態代理的步驟:
  1. 經過實現 InvocationHandler 接口建立本身的調用處理器; 
  2. 經過爲 Proxy 類指定 ClassLoader 對象和一組 interface 來建立動態代理類; 
  3. 經過反射機制得到動態代理類的構造函數,其惟一參數類型是調用處理器接口類型; 
  4. 經過構造函數建立動態代理類實例,構造時調用處理器對象做爲參數被傳入。
// InvocationHandlerImpl 實現了 InvocationHandler 接口,並能實現方法調用從代理類到委託類的分派轉發
// 其內部一般包含指向委託類實例的引用,用於真正執行分派轉發過來的方法調用
InvocationHandler handler = new InvocationHandlerImpl(..);

// 經過 Proxy 爲包括 Interface 接口在內的一組接口動態建立代理類的類對象
Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... });

// 經過反射從生成的類對象得到構造函數對象
Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class });

// 經過構造函數對象建立動態代理類實例
Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });

  實際使用過程更加簡單,由於 Proxy 的靜態方法 newProxyInstance 已經爲咱們封裝了步驟 2 到步驟 4 的過程:

// InvocationHandlerImpl 實現了 InvocationHandler 接口,並能實現方法調用從代理類到委託類的分派轉發
InvocationHandler handler = new InvocationHandlerImpl(..);

// 經過 Proxy 直接建立動態代理類實例
Interface proxy = (Interface)Proxy.newProxyInstance( classLoader,
                                                     new Class[] { Interface.class },
                                                     handler );

JAVA示例代碼:

public class TraceHandler implements InvocationHandler{
   private Object target = null;
   public TraceHandler(Object t) {
      this.target = t;
   }
 
   @Override
   public Object invoke(Object proxy, Method method, Object[] args)
       throws Throwable {
      System.out.print(target);
      System.out.print("." + method.getName() + "(");
      if(args != null) {
         for(int i = 0; i < args.length; ++i) {
            System.out.print(args[i]);
            if(i < args.length-1) System.out.print(", ");
         }
      }
      System.out.println(")");
      return method.invoke(target, args);
   }
}

Test.java

public void test() {
    Object[] elements = new Object[1000];
    for (int i = 0; i < elements.length; i++) {
       Integer val = i+1;
       TraceHandler handler = new TraceHandler(val);
       elements[i] = Proxy.newProxyInstance(null, new Class[]{Comparable.class}, handler);
    }
 
    Integer key = new Random().nextInt(1000) + 1;
    int result = Arrays.binarySearch(elements, key);
    if (result > 0) {
       System.out.println(elements[result]);
    }
 }

   Proxy 靜態方法生成動態代理類一樣須要經過類裝載器來進行裝載才能使用,它與普通類的惟一區別就是其字節碼是由 JVM 在運行時動態生成的而非預存在於任何一個 .class 文件中。每次生成動態代理類對象時都須要指定一個類裝載器對象。

動態生成的代理類自己的一些特色:

  1. 包:若是所代理的接口都是 public 的,那麼它將被定義在頂層包(即包路徑爲空),若是所代理的接口中有非 public 的接口(由於接口不能被定義爲 protect 或 private,因此除 public 以外就是默認的 package 訪問級別),那麼它將被定義在該接口所在包(假設代理了 com.ibm.developerworks 包中的某非 public 接口 A,那麼新生成的代理類所在的包就是 com.ibm.developerworks),這樣設計的目的是爲了最大程度的保證動態代理類不會由於包管理的問題而沒法被成功定義並訪問;
  2. 類修飾符:該代理類具備 final 和 public 修飾符,意味着它能夠被全部的類訪問,可是不能被再度繼承;
  3. 類名:格式是「$ProxyN」,其中 N 是一個逐一遞增的阿拉伯數字,表明 Proxy 類第 N 次生成的動態代理類,值得注意的一點是,並非每次調用 Proxy 的靜態方法建立動態代理類都會使得 N 值增長,緣由是若是對同一組接口(包括接口排列的順序相同)試圖重複建立動態代理類,它會很聰明地返回先前已經建立好的代理類的類對象,而不會再嘗試去建立一個全新的代理類,這樣能夠節省沒必要要的代碼重複生成,提升了代理類的建立效率。
代理類實例的一特色:       
       在代理類實例上調用其代理的接口中所聲明的方法時,這些方法最終都會由調用處理器的 invoke 方法執行,此外,值得注意的是,代理類的根類 java.lang.Object 中有三個方法也一樣會被分派到調用處理器的 invoke 方法執行,它們是 hashCode,equals 和 toString。
       當代理的一組接口有重複聲明的方法且該方法被調用時,代理類老是從排在最前面的接口中獲取方法對象並分派給調用處理器,而不管代理類實例是否正在以該接口(或繼承於該接口的某子接口)的形式被外部引用,由於在代理類內部沒法區分其當前的被引用類型。

被代理的接口的特色:

  1. 要注意不能有重複的接口,以免動態代理類代碼生成時的編譯錯誤。
  2. 這些接口對於類裝載器必須可見,不然類裝載器將沒法連接它們,將會致使類定義失敗。
  3. 需被代理的全部非 public 的接口必須在同一個包中,不然代理類生成也會失敗。
  4. 接口的數目不能超過 65535,這是 JVM 設定的限制。

異常處理的特色:

  代理類並不能拋出全部的異常,由於子類覆蓋父類或實現父接口的方法時,拋出的異常必須在原方法支持的異常列表以內。
      若是代理產生了接口方法中不支持的異常,它將會拋出 UndeclaredThrowableException 異常。這個異常是一個 RuntimeException 類型,因此不會引發編譯錯誤。經過該異常的 getCause 方法,還能夠得到原來那個不受支持的異常對象,以便於錯誤診斷。

基於CGLIB的動態代理

  因爲JDK的動態代理依靠接口實現,若是有些類並沒有實現接口,則不能使用JDK代理,這就要使用cglib動態代理了。

CGlib概述:

  • cglib(Code Generation Library)是一個強大的,高性能,高質量的Code生成類庫。它能夠在運行期擴展Java類與實現Java接口。
  • cglib封裝了asm,能夠在運行期動態生成新的class。
  • cglib用於AOP,jdk中的proxy必須基於接口,cglib卻沒有這個限制。

  JDK的動態代理機制只能代理實現了接口的類,而不能實現接口的類就不能實現JDK的動態代理,cglib是針對類來實現代理的,他的原理是對指定的目標類生成一個子類,並覆蓋其中方法實現加強,但由於採用的是繼承,因此不能對final修飾的類進行代理。 很少說,直接上代碼!

有一個Manager類:

public class Manager {

    public void query() {
        System.out.println("query...");
    }
    
    public void insert() {
        System.out.println("insert...");
    }
    
    public String update() {
        System.out.println("update....");
        return "I'm update";
    }
    
    public void delete() {
        System.out.println("delete....");
    }
}

咱們想要在這個類中的每一個方法前面和後面都打印一句話,這時候咱們就可使用代理了,讓咱們來看一下這個代理能夠怎麼寫:

public class AuthProxy implements MethodInterceptor{
    
    @Override
    public Object intercept(Object arg0, Method method, Object[] args,
            MethodProxy proxy) throws Throwable {
        System.out.println("Before...");
        Object result = proxy.invokeSuper(arg0, args);
        System.out.println("After....");
        return result;
    }
}

  如上,CGLIB實現的代理類必須實現MethodInterceptor接口,該接口中只有一個方法須要實現,即intercept方法。經過MethodProxy中的invokeSuper便可執行被代理類的方法。咱們繼續往下看。

public static Manager getInstace(AuthProxy auth) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Manager.class);
        enhancer.setCallback(auth);
        return (Manager) enhancer.create();
    }

  經過以上代碼咱們就能獲得一個Manager的代理類,被AuthProxy代理。

public void AuthProxyTest() {
        AuthProxy auth = new AuthProxy();
        Manager manager = ManagerFactory.getInstace(auth);
        manager.delete();
        System.out.println();
        manager.query();
        System.out.println();
        String result = manager.update();
        System.out.println("result: " + result);
    }

下面是一個能夠代理不一樣類的代理生成工廠:

public class ManagerFactory2 {
    
    public static Manager getInstace(Class clasz, AuthProxy auth) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clasz);
        enhancer.setCallback(auth);
        return (Manager) enhancer.create();
    }
}

對這個工廠進行通用化擴展:

public class ManagerFactory {
    
    public static Manager getInstace(Class clasz, AuthProxyFilter filter, AuthProxy auth, Object...args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clasz);
        
        Callback[] callback = new Callback[args.length+1];
        System.out.println("length: " + callback.length);
        callback[0] = auth;
        for (int i = 0; i < args.length; i++) {
            if (args[i] instanceof Callback) {
                callback[i+1] = (Callback) args[i];
            }else {
                callback[i+1] = NoOp.INSTANCE;
            }
        }
        
        enhancer.setCallbacks(callback);
        enhancer.setCallbackFilter(filter);
        return (Manager) enhancer.create();
    }
AuthProxyFilter.java
public class AuthProxyFilter implements CallbackFilter{

    @Override
    public int accept(Method method) {
        if ("query".equals(method.getName())) {
            return 1;
        }
        return 0;
    }
}

 =====================華麗的分割線==================================

                                              源碼請猛戳{ 這裏

===============================================================

參考資料:

http://www.cnblogs.com/jqyp/archive/2010/08/20/1805041.html

http://www.blogjava.net/stone2083/archive/2008/03/16/186615.html

相關文章
相關標籤/搜索