Java JDK動態代理Proxy類的原理是什麼?

本文轉載自知乎問題Java JDK動態代理Proxy類的原理是什麼?下用戶@胖君回答

  1. 什麼是代理?

先從代理開始講。
代理這種設計模式其實很好理解,基本就是最簡單的一個 “組合”。比如說下面這個例子,我們有 A 這個類,本來可以直接調用 A 類的 foo() 方法。但代理模式就非要把 A 類當成 B 類的一個成員字段放在 B 類裏面。然後因爲 A 類和 B 類都實現了 Interface 這個接口,所以 B 類裏也有 foo()方法。而且 B 類裏的 foo()方法就是傻瓜式的調用 A 類的 foo()方法。

interface Interface{public void foo();}
/**委託類*/
class A implements Interface{
    public void foo(){System.out.println("Method a of class A!");}
}
/**代理類*/
class B implements Interface{
    public A a=new A();
    public void foo(){a.foo();}
}
/**用戶*/
class Consumer{
    public static void consum(Interface i){
        i.foo();
    }
}
/**測試*/
public class TestProxy{
    public static void main(String[] args){
        Interface i=new B();
        Consumer.consum(i);
    }
}
  1. 代理有什麼好處?

乍一看,代理方法完全是多此一舉,B 類的行爲和 A 類完全一樣,沒有任何意義。但實際上,B 類的 foo() 方法在直接調用 A 類 foo() 方法之前和之後,可以做很多事情。舉個例子,如果在 B 類里加個靜態計數字段 count,然後每次調用 foo() 方法之後都計一下數,就可以監控 A 類 foo() 方法被調用的次數。

class B implements Interface{
    public static long count=0;
    public A a=new A();
    public void foo(){a.foo();count++;}
}

所以代理類裏能非常好地控制,輔助被代理類,甚至是增加額外的功能。而且一般來說代理類 B 和被代理 A 都會實現同樣的接口,這樣對用戶端(就是上面例子裏的 Consumer 類)的代碼沒有任何影響,耦合很低。

  1. 什麼是動態代理?

上面例子裏在 A 類外面套一個 B 類好像很簡單,但實際到了工程級別的代碼,需要代理的就不止一個兩個了。每個代理類都手動寫會累死,而且很枯燥,是沒有技術含量的重複。所以這個時候 Java 的反射功能就立功了。代理類 B 是可以完全用反射動態生成的。
怎麼動態生成 B 類呢?下面有個例子,通過反射動態加載 B 類,然後調用 B 類的 foo() 方法。

public class TestDynamicProxy{
    public static void main(String[] args){
        try{
            Class<?> refB=B.class;
            Method refFoo=refB.getDeclaredMethod("foo");
            Object refObj=refB.newInstance();
            refFoo.invoke(refObj);
        }catch(Exception e){
            System.out.println(e);
        }
    }
}
  1. B.class 獲得了 B 類的 Class 對象。
  2. Class#getDeclaredMethod() 方法根據方法的名稱"foo"獲得了 foo() 方法的 Method 對象。
  3. 最後調用這個 Method 對象的 invoke() 來執行這個方法。

但這個是動態生成嗎?明顯不是!上面這個方法只是在 B 類已經寫好了的情況下動態調用 B 類。其實並沒有動態生成 B 類,根本不能叫動態生成。真的要完全憑空用反射 “寫” 一個 B 類的字節碼文件出來然後加載它,其實要複雜地多,這就是爲什麼需要 Proxy 工具來替我們做的原因。

4.Proxy 類怎麼實現動態代理?

Proxy 類裏能替我們生成一個代理類對象的,就是 newProxyInstance() 方法。現在回過頭看它的三個參數,

newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
  1. 第一個 ClassLoader 是爲了生成 B 類的 Class 對象。作用是根據一組類的字節碼 byte[] 直接生成這個類的 Class 對象。
  2. 第二個參數是由委託類實現的接口的 Class 對象數組。主要是包含了最重要的代理類需要實現的接口方法的信息。
  3. 最後一個最重要的就是一個實現了 InvocationHandler 接口的對象。InvocationHandler 接口在 java.lang.reflect 包裏。最主要的就是定義了 invoke( ) 方法。它就是假設在已經動態生成了最後的 proxy 代理對象,以及所有接口定義的方法 Method 對象以及方法的參數的情況下,定義我們要怎麼調用這些方法的地方。

這三個參數的分工用大白話講就是:第一參數 ClassLoader 和第二參數接口的 Class 對象是用來動態生成委託類的包括類名,方法名,繼承關係在內的一個空殼。用 B 類的例子演示就像下面這樣,

class $ProxyN implements Interface{
    public void foo(){
        //do something...
    }
}

只有接口定義的方法名,沒有實際操作。實際的操作是交給第三個參數 InvocationHandlerinvoke() 方法來執行。

所以最主要的業務邏輯應該是在第三個參數 InvocationHandlerinvoke() 方法裏定義。下面代碼,是根據之前 A 類 B 類的例子用 Proxy 類實現動態代理的 Demo。代碼裏原先的 B 類被擦掉了,完全由 Proxy 類動態生成。

interface Interface{public void foo();}
class A implements Interface{
    public void foo(){System.out.println("Method a of class A!");}
}
/*    //這是Proxy要動態生成的B類。
class B implements Interface{
    public void foo(){a.foo();}
    public A a=new A();
}
 */
class Consumer{
    public static void consum(Interface i){
        i.foo();
    }
}
class DynamicProxyHandler implements InvocationHandler {
    private Object proxied;
    public DynamicProxyHandler(Object proxied) {
        this.proxied = proxied;
    }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try{
            return method.invoke(proxied, args);
        }catch(Exception e){
            System.out.println(e);
            return null;
        }
    }
}
public class TestDynamicProxy{
    public static void main(String[] args){
        A a=new A();
    //直接把A類對象a當參數傳進去,就動態產生一個代理類對象
        Interface proxy = (Interface)Proxy.newProxyInstance(Interface.class.getClassLoader(), new Class<?>[]{Interface.class }, new DynamicProxyHandler(a));
        Consumer.consum(proxy);
    }
}

在實現了 InvocationHandler 接口的 DynamicProxyHandler 類裏有一個被代理類的對象 proxied 作爲成員字段。在獲得了參數傳進來的代理類對象和 Method 對象之後,直接用 Method#invoke(Object o) 方法,調用了代理類對象的方法。第一個參數 ClassLoader 直接用的是 Interface 接口的類加載器 (Interface.class.getClassLoader())。第二參數就是 Interface 接口的 Class 對象。

然後,剩下的事就交給 Proxy 來完成。關鍵的難點在於怎麼根據給定的 ClassLoader 和接口的方法信息動態生成一個所謂 “空殼”。其實 newProxyInstance() 方法隱藏了非常多其他的複雜性,比如說訪問權限檢查,包路徑設置,安全檢查等等瑣碎的事,但這裏先不說。只說核心步驟。

下面截取 newProxyInstance() 方法源碼裏比較重要的一段貼上來,看看底層是怎麼實現的。

/*
 * Choose a name for the proxy class to generate.
 */
long num;
synchronized (nextUniqueNumberLock) {
    num = nextUniqueNumber++;
}
String proxyName = proxyPkg + proxyClassNamePrefix + num;
/*
 * Generate the specified proxy class.
 */
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
    proxyName, interfaces);
try {
    proxyClass = defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
     throw new IllegalArgumentException(e.toString());
}

可以看到,proxyName 是動態生成的代理類的名稱,一般是 ·$ProxyN 的格式。N 代表代理是 N 次生成動態代理。

然後見證奇蹟的時刻到了,關鍵的核心步驟有兩個:

  1. ProxyGenerator.generateProxyClass() 方法生成了類加載器要用到的字節碼。它需要的參數只有兩個,1)類名,2)實現的接口的 Class 對象。然後它就神奇地生成了一堆字節碼 byte[],基本就是一個憑空造出來的編譯好的. class 文件。這個方法來自神祕的 sun.misc 包。也查不到源碼。
  2. 最後神祕的字節碼和加載器,以及類名一起被交到另一個 native 方法 defineClass0( ) 裏,由它生成代理類的 Class 對象。至於 native 方法怎麼實現,源碼裏也查不到。

最後再總結一下,使用 Proxy 的三步,

  1. 在第三個參數,實現 InvocationHandler 接口的對象的 invoke() 方法裏寫業務邏輯。
  2. 第二個參數是代理實現接口的 Class 對象
  3. 第一個參數是一個 ClassLoader。一般直接用調用類的加載器

如果實在想知道鬼畜的 ProxyGenerator.generateProxyClass()的內部原理,就看誰能把人肉源碼機 R 大 @RednaxelaFX 召喚出來了,哈哈:v

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章