如下 JDK 源碼及 Javadoc 均從 java version "1.8.0_152" 版本實現中摘錄或翻譯java
代理模式即 Proxy Pattern,23 種經常使用的面向對象軟件的設計模式之一。代理模式爲其餘對象提供一種代理以控制對這個對象的訪問。在某些狀況下,一個對象不適合或者不能直接引用另外一個對象,而代理對象能夠在客戶端和目標對象之間起到中介的做用。代理模式由抽象角色、代理角色、真實角色組成。抽象角色經過接口或抽象類聲明真實角色實現的業務方法;代理角色實現抽象角色,是真實角色的代理,經過真實角色的業務邏輯方法來實現抽象方法,並能夠附加本身的操做;真實角色實現抽象角色,定義真實角色所要實現的業務邏輯,供代理角色調用。詳見代理模式類圖: 編程
動態代理即動態的代理模式,所謂動態,是指抽象類(即抽象角色)在編譯期是未肯定的,在運行期生成。相對的,靜態代理中抽象類的行爲是在編譯期肯定的。動態代理是 AOP(面向切面編程)常見的實現方式。設計模式
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
InvocationHandler#invoke
方法。不知各位使用 MyBatis 的時候有沒有疑問,爲何能夠直接調用接口?答案就在這裏,事實上,MyBatis 使用相似的技術,幫咱們實現了一個代理類,咱們拿到的都是接口的代理類實例。ide
爲了突出重點,如下代碼僅展現與主題相關的代碼,防護性編程、異常處理等無關內容已被省略,完整實現請自尋 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);
}
複製代碼
從代碼中,咱們能夠看到:
這裏的 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
生成的類字節碼有如下特色:
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。
Proxy.newProxyInstance
方法傳入類加載器、接口類對象、調用處理器來建立代理類實例ProxyGenerator.generateProxyClass
方法根據傳入接口類對象生成代理類的字節碼,並加載字節碼產生代理類對象