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