小豹子帶你看源碼:JDK 動態代理

如下 JDK 源碼及 Javadoc 均從 java version "1.8.0_152" 版本實現中摘錄或翻譯java

1 動態代理簡述

1.1 什麼是代理模式

代理模式即 Proxy Pattern,23 種經常使用的面向對象軟件的設計模式之一。代理模式爲其餘對象提供一種代理以控制對這個對象的訪問。在某些狀況下,一個對象不適合或者不能直接引用另外一個對象,而代理對象能夠在客戶端和目標對象之間起到中介的做用。代理模式由抽象角色、代理角色、真實角色組成。抽象角色經過接口或抽象類聲明真實角色實現的業務方法;代理角色實現抽象角色,是真實角色的代理,經過真實角色的業務邏輯方法來實現抽象方法,並能夠附加本身的操做;真實角色實現抽象角色,定義真實角色所要實現的業務邏輯,供代理角色調用。詳見代理模式類圖: 編程

代理模式類圖

1.2 什麼是動態代理

動態代理即動態的代理模式,所謂動態,是指抽象類(即抽象角色)在編譯期是未肯定的,在運行期生成。相對的,靜態代理中抽象類的行爲是在編譯期肯定的。動態代理是 AOP(面向切面編程)常見的實現方式。設計模式

1.3 動態代理使用示例

Java 的動態代理使用起來特別簡單,只須要咱們掌握 Proxy.newProxyInstance 方法便可。 Proxy.newProxyInstance 方法在 JDK 中定義以下:數組

/** * 返回一個受調用處理器 (InvocationHandler) 管理,實現了指定接口的代理類的實例 * * @param loader 聲明這個代理類的 ClassLoader * @param interfaces 代理類實現的接口列表 * @param h 處理代理類的調用的調用處理器 * @return 一個受調用處理器 (InvocationHandler) 管理,實現了指定接口的代理類的實例 * @throws IllegalArgumentException 違反了 getProxyClass 函數的參數限制條件 * @throws SecurityException 若是安全管理器存在而且下面的任意條件知足: * (1) 傳入的 loader 是 null 且調用者的類加載器非空, * 使用 RuntimePermission("getClassLoader")權限 * 調用 SecurityManager#checkPermission禁止訪問 * * (2) 對於每個代理接口,調用者的類加載器與接口類加載器不一樣或不是其父類, * 而且調用 SecurityManager#checkPackageAccess 無權訪問接口 * * (3) 全部傳入的代理接口都是非公共的,且調用者類與非公共接口不在同一個包下, * 使用 ReflectPermission("newProxyInPackage.{package name}") 調用 * SecurityManager#checkPermission 無訪問權限 * @throws NullPointerException interfaces 數組參數或其中的元素爲 null,以及調用處理器 h 爲 null */
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException;
複製代碼

從 Javadoc 中咱們能夠獲知,只須要傳入相應的類加載器,接口,調用處理器便可產生一個代理實例,那麼這裏咱們不熟悉的就是 InvocationHandler 類,咱們看一下 InvocationHandler 類的代碼:緩存

package java.lang.reflect;

/** * InvocationHandler是代理實例的調用處理器實現的接口。 * 每一個代理實例都有一個關聯的調用處理器。 * 在調用代理實例的方法時,方法調用將被編碼並分派給其調用處理程序的 invoke 方法。 * * @author Peter Jones * @see Proxy * @since 1.3 */
public interface InvocationHandler {

   /** * 在代理實例上處理方法調用並返回結果。當在與其關聯的代理實例上調用 * 方法時,將調用處理期上的此方法。 * * @param proxy 該方法被調用的代理實例 * * @param method Method 對象將是代理接口聲明的方法,它多是代理 * 類繼承方法的代理接口的超級接口。 * @param args 包含在代理實例的方法調用中傳遞的參數值的對象數組, * 若是interface方法不帶參數,則爲null。基本類型的參 * 數被封裝在適當的基本封裝類的實例中,好比 * java.lang.Integer 或者 java.lang.Boolean。 * @return 調用代理實例上的方法得到的返回值。若是接口方法的聲明返 * 回類型是基本類型,則此方法返回的值必須是相應基本包裝類 * 的實例;不然,它必須是轉換爲聲明的返回類型的類型。若是 * 此方法返回的值爲null,而且接口方法的返回類型爲原始類型, * 則代理實例上的方法調用將引起NullPointerException。若是 * 此方法返回的值與上面所述的接口方法的聲明返回類型不兼容, * 則將經過代理實例上的方法調用拋出ClassCastException。 * * @throws 拋出調用代理實例的方法時拋出的異常。異常的類型必須能夠 * 轉化爲接口方法的 throws 子句中聲明的異常類型,也能夠分 * 配給不強制檢查的異常類型 java.lang.RuntimeException 或 * java.lang.Error。若是這個方法拋出一個強制檢查的異常, * 這個異常不能轉化爲接口方法的 throws 子句中聲明的異常類 * 型,那麼將會拋出包含這個異常的 * UndeclaredThrowableException 異常。 * * @see UndeclaredThrowableException */
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

複製代碼

從 Javadoc 中咱們知道,調用經過 Proxy.newProxyInstance 方法建立的代理實例中的方法時,會執行傳入的 InvocationHandler#invoke 方法,代理實例中方法返回值爲 InvocationHandler#invoke 方法返回值。
咱們作一個小測試:安全

package com.example.demo;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Main {
    /** * 代理接口 */
    interface ITest {
        String test(String val);
    }

    /** * 代理實現類 */
    class Test implements ITest {
        @Override
        public String test(String val) {
            return val + "我是Test";
        }
    }

    /** * 調用處理器 */
    class TestInvocationHandler implements InvocationHandler {

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println(method);
            return args[0] + "我是TestProxy";
        }
    }

    /** * 分別對正常實現的 ITest 實現類和動態代理實現類進行調用 */
    void testProxy() {
        ITest test = new Test();
        ITest testProxy = (ITest) Proxy.newProxyInstance(this.getClass().getClassLoader(),
                new Class[]{ITest.class}, new TestInvocationHandler());
        System.out.println(test.test("Hello,"));
        System.out.println("----------");
        System.out.println(testProxy.test("Hello,"));
    }

    public static void main(String[] args) {
        new Main().testProxy();
    }

}

複製代碼

輸出結果爲:bash

Hello,我是Test
----------
public abstract java.lang.String com.example.demo.Main$ITest.test(java.lang.String)
Hello,我是TestProxy
複製代碼

從測試例子中,咱們能夠看到兩個特色:app

  • 實現了 ITest 接口的實現類並不須要咱們手動寫,是自動生成並實例化的。
  • 調用自動生成的 ITest 代理類實例,將調用 InvocationHandler#invoke 方法。

不知各位使用 MyBatis 的時候有沒有疑問,爲何能夠直接調用接口?答案就在這裏,事實上,MyBatis 使用相似的技術,幫咱們實現了一個代理類,咱們拿到的都是接口的代理類實例。ide

2 Java 動態代理實現原理

爲了突出重點,如下代碼僅展現與主題相關的代碼,防護性編程、異常處理等無關內容已被省略,完整實現請自尋 JDK函數

那麼 Java 的動態代理是怎樣實現的呢?這引發了個人好奇心。因此,咱們去看 JDK 源碼。
查看 Proxy.newProxyInstance 的實現:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException {
    
    final Class<?>[] intfs = interfaces.clone();
    // 經過類加載器和接口使用 getProxyClass0 方法建立實現類
    Class<?> cl = getProxyClass0(loader, intfs);
    // 得到指定構造器
    final Constructor<?> cons = cl.getConstructor(constructorParams);
    // 建立實例
    return cons.newInstance(new Object[]{h});
}
複製代碼

其中兩句建立實例的過程都是常見的反射操做,這裏不贅述,值得咱們好奇的是 getProxyClass0 方法是如何經過接口建立類的?咱們繼續跟進 getProxyClass0 方法的實現:

private static Class<?> getProxyClass0(ClassLoader loader,
                                    Class<?>... interfaces) {
    return proxyClassCache.get(loader, interfaces);
}
複製代碼

咱們跟進至 proxyClassCache.get 的實現,這應該是一個負責緩存管理的類:

public V get(K key, P parameter) {
    // Cache 置換、檢查等實現均已省略,如下是 Cache 未命中時,建立新實現類的代碼
    Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
    V value = supplier.get();
    return value;
}
複製代碼

咱們跟進至 ProxyClassFactory#apply 的實現:

public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
    for (Class<?> intf : interfaces) {
        interfaceClass = Class.forName(intf.getName(), false, loader);
        // 對 interfaceClass 進行了系列權限檢查,實現略
    }
    // 根據 interfaces、accessFlags 產生名爲 proxyName 的代理類字節碼
    byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, 
                                                interfaces, accessFlags);
    // 加載字節碼,產生類對象
    return defineClass0(loader, proxyName, proxyClassFile, 
                                                0, proxyClassFile.length);
}
複製代碼

從代碼中,咱們能夠看到:

  • ProxyGenerator.generateProxyClass 用於產生代理類的字節碼
  • defineClass0 用於加載字節碼產生類對象

這裏的 defineClass0 是一個 native 方法,咱們不深究。 ProxyGenerator.generateProxyClass 是對字節碼進行操做,因爲字節碼的規範我不是很懂,因此此處,咱們去看他的代碼實現對咱們幫助不大。那麼怎麼辦呢?咱們作一個小實驗:

public class Main2 {
    /** * 代理接口 */
    interface ITest {
        String test(String val);
    }

    public static void main(String[] args) throws IOException {
        // 經過 ProxyGenerator.generateProxyClass 產生字節碼
        byte[] testProxyBytes = ProxyGenerator.generateProxyClass("TestProxy", new Class[]{ITest.class});
        // 將字節碼輸出到文件,而後咱們再反編譯它,看看它的內容是什麼
        FileOutputStream fileOutputStream = new FileOutputStream("TestProxy.class");
        fileOutputStream.write(testProxyBytes);
        fileOutputStream.flush();
        fileOutputStream.close();
    }
}
複製代碼

TestProxy.class 反編譯後的源碼:

public final class TestProxy extends Proxy implements ITest {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public TestProxy(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);
        }
    }

    public final String test(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);
        }
    }

    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("com.example.demo.Main2$ITest").getMethod("test", Class.forName("java.lang.String"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}
複製代碼

經過 ProxyGenerator.generateProxyClass 生成的類字節碼有如下特色:

  • 該類繼承了 Proxy 實現了傳入接口類(ITest)
  • 該類在 static 代碼塊中定義了全部該類包含的方法的 Method 實例。
  • 該類有一個構造器 TestProxy(InvocationHandler var1) 傳入調用處理器。
  • 該類全部方法都將執行 super.h.invoke 並返回其結果。

那麼這裏的 super.h 是什麼呢,咱們看其父類 Proxy 的代碼:

protected InvocationHandler h;
protected Proxy(InvocationHandler h) {
    Objects.requireNonNull(h);
    this.h = h;
}
複製代碼

恍然大悟!這裏的 super.h 就是 TestProxy(InvocationHandler var1) 構造器中傳入的 h。

3 總結

  • 用戶經過 Proxy.newProxyInstance 方法傳入類加載器、接口類對象、調用處理器來建立代理類實例
  • JDK 中經過 ProxyGenerator.generateProxyClass 方法根據傳入接口類對象生成代理類的字節碼,並加載字節碼產生代理類對象
  • 生成的代理類繼承了 Proxy 實現了傳入接口類
  • 該類每個方法都會執行調用處理器的 invoke 方法,傳入相應參數,返回 invoke 方法的返回值
相關文章
相關標籤/搜索