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

LeopPro發表於2018-01-10

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

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

}

複製程式碼

輸出結果為:

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

從測試例子中,我們可以看到兩個特點:

  • 實現了 ITest 介面的實現類並不需要我們手動寫,是自動生成並例項化的。
  • 呼叫自動生成的 ITest 代理類例項,將呼叫 InvocationHandler#invoke 方法。

不知各位使用 MyBatis 的時候有沒有疑問,為什麼可以直接呼叫介面?答案就在這裡,事實上,MyBatis 使用類似的技術,幫我們實現了一個代理類,我們拿到的都是介面的代理類例項。

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 方法的返回值

相關文章