JDK動態代理:不只要學會用,更要掌握其原理

JDK動態代理:不只要學會用,更要掌握其原理

微信搜索:碼農StayUp
主頁地址:https://gozhuyinglong.github.io
源碼分享:https://github.com/gozhuyinglong/blog-demosjava

JDK動態代理是指:代理類實例在程序運行時,由JVM根據反射機制動態的生成。也就是說代理類不是用戶本身定義的,而是由JVM生成的。git

因爲其原理是經過Java反射機制實現的,因此在學習前,要對反射機制有必定的瞭解。傳送門:Java反射機制:跟着代碼學反射github

下面是本篇講述內容:
JDK動態代理:不只要學會用,更要掌握其原理數組

1. JDK動態代理的核心類

JDK動態代理有兩大核心類,它們都在Java的反射包下(java.lang.reflect),分別爲InvocationHandler接口和Proxy類。緩存

1.1 InvocationHandler接口

代理實例的調用處理器須要實現InvocationHandler接口,而且每一個代理實例都有一個關聯的調用處理器。當一個方法在代理實例上被調用時,這個方法調用將被編碼並分派到其調用處理器的invoke方法上。微信

也就是說,咱們建立的每個代理實例都要有一個關聯的InvocationHandler,而且在調用代理實例的方法時,會被轉到InvocationHandlerinvoke方法上。ide

public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;

invoke方法的做用是:處理代理實例上的方法調用並返回結果。函數

其有三個參數,分別爲:學習

  • proxy:是調用該方法的代理實例。
  • method:是在代理實例上調用的接口方法對應的Method實例。
  • args:一個Object數組,是在代理實例上的方法調用中傳遞的參數值。若是接口方法爲無參,則該值爲null。

其返回值爲:調用代理實例上的方法的返回值。this

1.2 Proxy類

Proxy類提供了建立動態代理類及其實例的靜態方法,該類也是動態代理類的超類。

代理類具備如下屬性:

  • 代理類的名稱以 「$Proxy」 開頭,後面跟着一個數字序號。
  • 代理類繼承了Proxy類。
  • 代理類實現了建立時指定的接口(JDK動態代理是面向接口的)。
  • 每一個代理類都有一個公共構造函數,它接受一個參數,即接口InvocationHandler的實現,用於設置代理實例的調用處理器。

Proxy提供了兩個靜態方法,用於獲取代理對象。

1.2.1 getProxyClass

用於獲取代理類的Class對象,再經過調用構造函數建立代理實例。

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

該方法有兩個參數:

  • loader:爲類加載器。
  • intefaces:爲接口的Class對象數組。

返回值爲動態代理類的Class對象。

1.2.2 newProxyInstance

用於建立一個代理實例。

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

該方法有三個參數:

  • loader:爲類加載器。
  • interfaces:爲接口的Class對象數組。
  • h:指定的調用處理器。

返回值爲指定接口的代理類的實例。

1.3 小結

Proxy類主要用來獲取動態代理對象,InvocationHandler接口主要用於方法調用的約束與加強。

2. 獲取代理實例的代碼示例

上一章中已經介紹了獲取代理實例的兩個靜態方法,如今經過代碼示例來演示具體實現。

2.1 建立目標接口及其實現類

JDK動態代理是基於接口的,咱們建立一個接口及其實現類。

Foo接口:

public interface Foo {

    String ping(String name);

}

Foo接口的實現類RealFoo:

public class RealFoo implements Foo {

    @Override
    public String ping(String name) {
        System.out.println("ping");
        return "pong";
    }

}

2.2 建立一個InvocationHandler

建立一個InvocationHandler接口的實現類MyInvocationHandler。該類的構造方法參數爲要代理的目標對象。

invoke方法中的三個參數上面已經介紹過,經過調用methodinvoke方法來完成方法的調用。

這裏一時看不懂不要緊,後面源碼解析章節會進行剖析。

public class MyInvocationHandler implements InvocationHandler {

    // 目標對象
    private final Object target;

    public MyInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("proxy - " + proxy.getClass());
        System.out.println("method - " + method);
        System.out.println("args - " + Arrays.toString(args));
        return method.invoke(target, args);
    }
}

2.3 方式一:經過getProxyClass方法獲取代理實例

具體實現步驟以下:

  1. 根據類加載器和接口數組獲取代理類的Class對象
  2. 過Class對象的構造器建立一個實例(代理類的實例)
  3. 將代理實例強轉成目標接口Foo(由於代理類實現了目標接口,因此能夠強轉)。
  4. 最後使用代理進行方法調用。
@Test
public void test1() throws Exception {
    Foo foo = new RealFoo();
    // 根據類加載器和接口數組獲取代理類的Class對象
    Class<?> proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), Foo.class);

    // 經過Class對象的構造器建立一個實例(代理類的實例)
    Foo fooProxy = (Foo) proxyClass.getConstructor(InvocationHandler.class)
        .newInstance(new MyInvocationHandler(foo));

    // 調用 ping 方法,並輸出返回值
    String value = fooProxy.ping("楊過");
    System.out.println(value);

}

輸出結果:

proxy - class com.sun.proxy.$Proxy4
method - public abstract java.lang.String io.github.gozhuyinglong.proxy.Foo.ping(java.lang.String)
args - [楊過]
ping
pong

經過輸出結果能夠看出:

  • 代理類的名稱是以$Proxy開頭的。
  • 方法實例爲代理類調用的方法。
  • 參數爲代理類調用方法時傳的參數。

2.4 方式二:經過newProxyInstance方法獲取代理實例

經過這種方法是最簡單的,也是推薦使用的,經過該方法能夠直接獲取代理對象。

注:其實該方法後臺實現實際與上面使用getProxyClass方法的過程同樣。

@Test
public void test2() {
    Foo foo = new RealFoo();
    // 經過類加載器、接口數組和調用處理器,建立代理類的實例
    Foo fooProxy = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
                                                new Class[]{Foo.class},
                                                new MyInvocationHandler(foo));
    String value = fooProxy.ping("小龍女");
    System.out.println(value);
}

2.5 經過Lambda表達式簡化實現

其實InvocationHander接口也不用建立一個實現類,可使用Lambad表達式進行簡化的實現,以下代碼:

@Test
public void test3() {
    Foo foo = new RealFoo();

    Foo fooProxy = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
                                                new Class[]{Foo.class},
                                                (proxy, method, args) -> method.invoke(foo, args));
    String value = fooProxy.ping("雕兄");
    System.out.println(value);
}

3. 源碼解析

3.1 代理類$Proxy是什麼樣子

JVM爲咱們自動生成的代理類究竟是什麼樣子的呢?下面咱們先來生成一下,再來看裏面的構造。

3.1.1 生成$Proxy的.class文件

JVM默認不建立該.class文件,須要增長一個啓動參數:
-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true

在IDEA中點擊【Edit Configurations...】,打開 Run/Debug Configurations 配置框。

JDK動態代理:不只要學會用,更要掌握其原理
將上面啓動參數加到【VM options】中,點擊【OK】便可。
JDK動態代理:不只要學會用,更要掌握其原理

再次運行代碼,會在項目中的【com.sun.proxy】目錄中找到這個.class文件,我這裏是「$Proxy4.class」
JDK動態代理:不只要學會用,更要掌握其原理

3.1.2 爲何加上這段啓動參數就能生成$Proxy的字節碼文件

Proxy類中有個ProxyClassFactory靜態內部類,該類主要做用就是生成靜態代理的。

其中有一段代碼ProxyGenerator.generateProxyClass用來生成代理類的.class文件。
JDK動態代理:不只要學會用,更要掌握其原理

其中變量saveGeneratedFiles即是引用了此啓動參數的值。將該啓動參數配置爲true會生成.class文件。
JDK動態代理:不只要學會用,更要掌握其原理

3.1.3 這個代理類$Proxy究竟是什麼樣子呢

神祕的面紗即將揭露,前面不少未解之迷在這裏能夠找到答案!

打開這個$Proxy文件,我這裏生成的是$Proxy4,下面是內容:

// 該類爲final類,其繼承了Proxy類,並實現了被代理接口Foo
public final class $Proxy4 extends Proxy implements Foo {

    // 這4個Method實例,表明了本類實現的4個方法
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    // 靜態代碼塊根據反射獲取這4個方法的Method實例
    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("io.github.gozhuyinglong.proxy.Foo").getMethod("ping");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }

    // 一個公開的構造函數,參數爲指定的 InvocationHandler 
    public $Proxy4(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    // Foo接口的實現方法,最終調用了 InvocationHandler 中的 invoke 方法
    public final String ping(String var1) throws  {
        try {
            return (String)super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
}

經過該文件能夠看出:

  • 代理類繼承了Proxy類,其主要目的是爲了傳遞InvocationHandler
  • 代理類實現了被代理的接口Foo,這也是爲何代理類能夠直接強轉成接口的緣由。
  • 有一個公開的構造函數,參數爲指定的InvocationHandler,並將參數傳遞到父類Proxy中。
  • 每個實現的方法,都會調用InvocationHandler中的invoke方法,並將代理類自己、Method實例、入參三個參數進行傳遞。這也是爲何調用代理類中的方法時,總會分派到InvocationHandler中的invoke方法的緣由。

3.2 代理類是如何建立的

咱們從Proxy類爲咱們提供的兩個靜態方法開始getProxyClassnewProxyInstance。上面已經介紹了,這兩個方法是用來建立代理類及其實例的,下面來看源碼。

3.2.1 getProxyClass 和 newProxyInstance方法

getProxyClass方法

newProxyInstance方法

經過上面源碼能夠看出,這兩個方法最終都會調用getProxyClass0方法來生成代理類的Class對象。只不過newProxyInstance方法爲咱們建立好了代理實例,而getProxyClass方法須要咱們本身建立代理實例。

3.2.2 getProxyClass0 方法

下面來看這個統一的入口:getProxyClass0
getProxyClass0方法

從源碼和註解能夠看出:

  • 代理接口的最多不能超過65535個
  • 會先從緩存中獲取代理類,則沒有再經過ProxyClassFactory建立代理類。(代理類會被緩存一段時間。)

3.2.3 WeakCache類

這裏簡單介紹一下WeakCache&lt;K, P, V&gt;類,該類主要是爲代理類進行緩存的。獲取代理類時,會首先從緩存中獲取,若沒有會調用ProxyClassFactory類進行建立,建立好後會進行緩存。
WeakCache類的get方法

3.2.4 ProxyClassFactory類

ProxyClassFactoryProxy類的一個靜態內部類,該類用於生成代理類。下圖是源碼的部份內容:

ProxyClassFactory類

  • 代理類的名稱就是在這裏定義的,其前綴是$Proxy,後綴是一個數字。
  • 調用ProxyGenerator.generateProxyClass來生成指定的代理類。
  • defineClass0方法是一個native方法,負責字節碼加載的實現,並返回對應的Class對象。

3.3 原理圖

爲了便於記錄,將代理類的生成過程整理成了一張圖。

JDK動態代理:不只要學會用,更要掌握其原理

源碼分享

完整代碼請訪問個人Github,若對你有幫助,歡迎給個⭐,感謝~~

相關文章
相關標籤/搜索