1. 程式人生 > >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