1. 程式人生 > >java動態代理原理

java動態代理原理

一、代理的概念

  動態代理技術是整個java技術中最重要的一個技術它是學習java框架的基礎,不會動態代理技術,那麼在學習Spring這些框架時是學不明白的。

  動態代理技術就是用來產生一個物件的代理物件的在開發中為什麼需要為一個物件產生代理物件呢?
  舉一個現實生活中的例子:歌星或者明星都有一個自己的經紀人,這個經紀人就是他們的代理人,當我們需要找明星表演時,不能直接找到該明星,只能是找明星的代理人。比如劉德華在現實生活中非常有名,會唱歌,會跳舞,會拍戲,劉德華在沒有出名之前,我們可以直接找他唱歌,跳舞,拍戲,劉德華出名之後,他乾的第一件事就是找一個經紀人,這個經紀人就是劉德華的代理人(代理),當我們需要找劉德華表演時,不能直接找到劉德華了(劉德華說,你找我代理人商談具體事宜吧!),只能是找劉德華的代理人,因此劉德華這個代理人存在的價值就是攔截我們對劉德華的直接訪問!
  這個現實中的例子和我們在開發中是一樣的,我們在開發中之所以要產生一個物件的代理物件,主要用於攔截對真實業務物件的訪問。那麼代理物件應該具有什麼方法呢?代理物件應該具有和目標物件相同的方法。

  所以在這裡明確代理物件的兩個概念:
    1、代理物件存在的價值主要用於攔截對真實業務物件的訪問
    2、代理物件應該具有和目標物件(真實業務物件)相同的方法

    劉德華(真實業務物件)會唱歌,會跳舞,會拍戲,我們現在不能直接找他唱歌,跳舞,拍戲了,只能找他的代理人(代理物件)唱歌,跳舞,拍戲,一個人要想成為劉德華的代理人,那麼他必須具有和劉德華一樣的行為(會唱歌,會跳舞,會拍戲),劉德華有什麼方法,他(代理人)就要有什麼方法,我們找劉德華的代理人唱歌,跳舞,拍戲,但是代理人不是真的懂得唱歌,跳舞,拍戲的,真正懂得唱歌,跳舞,拍戲的是劉德華,在現實中的例子就是我們要找劉德華唱歌,跳舞,拍戲,那麼只能先找他的經紀人,交錢給他的經紀人,然後經紀人再讓劉德華去唱歌,跳舞,拍戲。

二、java中的代理

         使用代理模式必須要讓代理類和目標類實現相同的介面,客戶端通過代理類來呼叫目標方法,代理類會將所有的方法呼叫,分派到目標物件上反射執行,還可以在分派過程中新增"前置通知"和後置處理(是在invoke方法中新增,如在呼叫目標方法前校驗許可權,在呼叫完目標方法後列印日誌等)等功能。

 

使用動態代理的五大步驟

1.通過實現InvocationHandler介面來自定義自己的InvocationHandler(主要是編寫invoke方法);

2.通過Proxy.getProxyClass獲得動態代理類

3.通過反射機制獲得代理類的構造方法,方法簽名為getConstructor(InvocationHandler.class)

4.通過建構函式獲得代理物件並將自定義的InvocationHandler例項物件傳為引數傳入

 

5.通過代理物件呼叫目標方法

2.1、"java.lang.reflect.Proxy"類介紹

  現在要生成某一個物件的代理物件,這個代理物件通常也要編寫一個類來生成,所以首先要編寫用於生成代理物件的類。在java中如何用程式去生成一個物件的代理物件呢,java在JDK1.5之後提供了一個"java.lang.reflect.Proxy"類,通過"Proxy"類提供的一個newProxyInstance方法用來建立一個物件的代理物件,如下所示:

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

  newProxyInstance方法用來返回一個代理物件,這個方法總共有3個引數:

          ClassLoader loader:用來指明生成代理物件使用哪個類裝載器

          Class<?>[] interfaces:用來指明生成哪個物件的代理物件(代理類和目標類實現相同的介面

        InvocationHandler h : 用來指明產生的這個代理物件要做什麼事情。(這個InvocationHandler 就是我們自己定義的InvocationHandler 物件,裡邊有我們編寫invok方法

 

newProxyInstance方法生成代理物件過程:

    a. 通過Proxy.getProxyClass(ProxyGenerator.generateProxyClass(proxyName, interfaces);)獲得動態代理類的class位元組碼內容。
    b. 把位元組碼通過傳入的類載入器載入到虛擬機器中,然後通過反射機制獲得代理類的構造方法(方法簽名為getConstructor(InvocationHandler.class)),生成代理類物件

 

 

      那為什麼通過呼叫newProxyInstance方法得到的代理物件,即可以實現攔截新增前置、後置處理,有可以呼叫目標類的方法呢?

    輸出通過呼叫newProxyInstance方法得到的代理物件的位元組碼,儲存成class檔案,在反編譯成Java 程式碼,我們分析下生成的這個代理物件的Proxy0.java程式碼:

  public final void dance(String paramString)
  {
    try
    {
      h.invoke(this, m4, new Object[] { paramString });
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final void sing(String paramString)
  {
    try
    {
      h.invoke(this, m3, new Object[] { paramString });
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

///////

static{
    m3 = Class.forName("com.sun.proxy.$Proxy0").getMethod("sing", new Class[] { Class.forName("java.lang.String") });
    m4 = Class.forName("com.sun.proxy.$Proxy0").getMethod("dance", new Class[] { Class.forName("java.lang.String") });
      
    m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
    m6 = Class.forName("com.sun.proxy.$Proxy0").getMethod("getInvocationHandler", new Class[] { Class.forName("java.lang.Object") });
    m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
    m7 = Class.forName("com.sun.proxy.$Proxy0").getMethod("getProxyClass", new Class[] { Class.forName("java.lang.ClassLoader"), Class.forName("[Ljava.lang.Class;") });
    
    m12 = Class.forName("com.sun.proxy.$Proxy0").getMethod("getClass", new Class[0]);
    m14 = Class.forName("com.sun.proxy.$Proxy0").getMethod("notifyAll", new Class[0]);
    m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
    m9 = Class.forName("com.sun.proxy.$Proxy0").getMethod("wait", new Class[0]);
    m13 = Class.forName("com.sun.proxy.$Proxy0").getMethod("notify", new Class[0]);
    m8 = Class.forName("com.sun.proxy.$Proxy0").getMethod("newProxyInstance", new Class[] { Class.forName("java.lang.ClassLoader"), Class.forName("[Ljava.lang.Class;"), Class.forName("java.lang.reflect.InvocationHandler") });
    m11 = Class.forName("com.sun.proxy.$Proxy0").getMethod("wait", new Class[] { Long.TYPE });
    m5 = Class.forName("com.sun.proxy.$Proxy0").getMethod("isProxyClass", new Class[] { Class.forName("java.lang.Class") });
    m10 = Class.forName("com.sun.proxy.$Proxy0").getMethod("wait", new Class[] { Long.TYPE, Integer.TYPE });
}

dance(String paramString) 與之相呼應:代理類和目標類實現相同的介面

h.invoke(this, m4, new Object[] { paramString });    //m4:代理類的方法,和目標類的方法,一一對應。

 m4 = Class.forName("com.sun.proxy.$Proxy0").getMethod("dance", new Class[] { Class.forName("java.lang.String") });

與之相呼應: 這裡呼叫的是我們自定義的InvocationHandler 物件h的invoke方法,所以就實現了可以攔截、新增前置後置處理。

       

       這也說明了代理邏輯 和 動態代理本身是程式碼分離的,程式設計師只需要關注好自己的代理邏輯就行,動態代理本身就交給jdk本身去處理。在jdk動態代理中,美中不足就是整個設計都是針對介面做的代理,如果是普通的類,我們無法通過這個方式代理物件(通過生成的代理類也知道沒有介面是不行的),但是我們知道 通過拼接位元組碼生成新的類自由度是十分大的,這也就啟示我們 設計不管是針對介面類還是普通類的代理類 是完全可行的,比如cglib框架就是通過拼接位元組碼來實現非介面類的代理 後面會介紹如何 實現這種操作,我在自己的Simplify-Core專案中已經嘗試通過asm框架(位元組碼讀寫框架)實現代理操作。
 

參考文章:http://www.cnblogs.com/MOBIN/p/5597215.html

                  http://www.importnew.com/23168.html