友快網

導航選單

硬核資源!阿里內部流傳的 JDK 手冊!GitHub 已獲三十萬的訪問量

深入 JDK 動態代理實現

基本使用

沒有花裡胡哨的,就下面的程式碼:

UserService userService = new UserServiceImpl();

UserService o = (UserService) Proxy。newProxyInstance(UserService。class。getClassLoader(), new Class[]{UserService。class}, new InvocationHandler() {

@Override

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

System。out。println(“——before——”);

Object invoke = method。invoke(userService, args);

System。out。println(“——after——”);

return invoke;

}

});

可以看出,關鍵在於代理的建立:Proxy。newProxyInstance。後面我們也以這裡為入口深入。

關鍵步驟解析

生成代理類

生成代理類是動態代理中的最複雜的步驟,而真正生成代理類位元組碼的方法位於:java。lang。reflect。Proxy。ProxyClassFactory#apply。

精簡一下方法,主要有以下幾個步驟:

//前置校驗

interfaceClass = Class。forName(intf。getName(), false, loader);

if(xxx){

throw new IllegalArgumentException(xxx);

}

。。。

//資料準備

int accessFlags = Modifier。PUBLIC | Modifier。FINAL;

。。。

String proxyName = proxyPkg + proxyClassNamePrefix + num;

。。。

//生成代理類 bytes

byte[] proxyClassFile = ProxyGenerator。generateProxyClass(

proxyName, interfaces, accessFlags);

。。。

//載入

return defineClass0(loader, proxyName,

proxyClassFile, 0, proxyClassFile。length);

生成代理類與載入是動態代理的核心。

組裝 ProxyMethod

Step 1: Assemble ProxyMethod objects for all methods to generate proxy dispatching code for。

這裡只是將收集到的方法元資訊封裝為sun。misc。ProxyGenerator。ProxyMethod 類儲存,並沒有真正的生成 code。

這裡還特地也儲存了了hashCode,equals,toString方法。

addProxyMethod(hashCodeMethod, Object。class);

addProxyMethod(equalsMethod, Object。class);

addProxyMethod(toStringMethod, Object。class);

。。。

sigmethods。add(new ProxyMethod(name, parameterTypes, returnType,

exceptionTypes, fromClass));

組裝 FieldInfo / MethodInfo

Step 2: Assemble FieldInfo and MethodInfo structs for all of fields and methods in the class we are generating。

新增構造方法,並根據上一步組裝好的ProxyMethod新增成員屬性。

在這一步中,會呼叫sun。misc。ProxyGenerator。ProxyMethod#generateMethod方法,為其生成code。

//生成構造方法

this。methods。add(this。generateConstructor());

。。。

//生成成員屬性

fields。add(new FieldInfo(pm。methodFieldName,

“Ljava/lang/reflect/Method;”,

ACC_PRIVATE | ACC_STATIC));

//新增方法

methods。add(pm。generateMethod());

。。。

//新增靜態初始化程式碼塊

methods。add(generateStaticInitializer());

構造最終類

Step 3: Write the final class file。

根據上面幾步收集到的資訊,生成最終 byte 陣列。

// u4 magic;

dout。writeInt(0xCAFEBABE);

// u2 minor_version;

dout。writeShort(CLASSFILE_MINOR_VERSION);

// u2 major_version;

dout。writeShort(CLASSFILE_MAJOR_VERSION);

cp。write(dout); // (write constant pool)

// u2 access_flags;

dout。writeShort(accessFlags);

// u2 this_class;

dout。writeShort(cp。getClass(dotToSlash(className)));

// u2 super_class;

dout。writeShort(cp。getClass(superclassName));

。。。

最終結果

透過配置 sun。misc。ProxyGenerator。saveGeneratedFiles=true 可以將生成的代理類位元組碼儲存下來。

刪除了我們無需關心 hashCode,toString等方法後,反編譯結果如下:

package com。sun。proxy;

import git。frank。proxy。UserService;

import java。lang。reflect。InvocationHandler;

import java。lang。reflect。Method;

import java。lang。reflect。Proxy;

import java。lang。reflect。UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements UserService {

private static Method m1;

private static Method m2;

private static Method m3;

private static Method m0;

public $Proxy0(InvocationHandler var1) throws  {

super(var1);

}

public final String getUserInfo() throws  {

try {

return (String)super。h。invoke(this, m3, (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(“git。frank。proxy。UserService”)。getMethod(“getUserInfo”);

m0 = Class。forName(“java。lang。Object”)。getMethod(“hashCode”);

} catch (NoSuchMethodException var2) {

throw new NoSuchMethodError(var2。getMessage());

} catch (ClassNotFoundException var3) {

throw new NoClassDefFoundError(var3。getMessage());

}

}

}

載入代理類

我們以 openJdk 中的程式碼為參考,這裡實際呼叫的方法是:JavaLangAccess#defineClass。

Defines a class with the given name to a class loader。

它真正的程式碼位於 java。lang。System 中,以匿名內部類的形式。

// Allow privileged classes outside of java。lang

private static void setJavaLangAccess() {

SharedSecrets。setJavaLangAccess(new JavaLangAccess() {

。。。

public Class<?> defineClass(ClassLoader loader, String name, byte[] b, ProtectionDomain pd, String source) {

return ClassLoader。defineClass1(loader, name, b, 0, b。length, pd, source);

}

。。。

}

所以,這裡就是使用給定的類載入器去載入剛才生成的class。當被代理方法getUserInfo被呼叫時,便會被代理類轉至傳入的 InvocationHandler#invoke 方法中。

那麼,為什麼在程式碼中沒有直接呼叫 ClassLoader.defineClass1,而是這樣繞了一圈呢?

我們再仔細看看,因為 ClassLoader 中的 defineClass 相關程式碼都是 protected 的呀~,根本沒辦法直接呼叫。

所以 openJdk 才創造了一個 JavaLangAccess 類,專門用於讓外界呼叫 java。lang 包內的內容。

構造物件

構造物件就簡單很多,單純地使用反射呼叫構造方法去建立剛才生成的代理類例項。

使用入參為 InvocationHandler 的構造方法,並傳入使用者編寫的 InvocationHandler 物件。

final Constructor<?> cons = cl。getConstructor(constructorParams);

。。。

cons。newInstance(new Object[]{h})

這裡構造的實際上就是上面生成的 com。sun。proxy。$Proxy0 類例項。

動態代理中的快取機制

使用動態代理時並不是每次都要經過上面那一大串程式碼的,只要同 classLoader + interfaces 相同,都可以使用同一個 proxy 類。

所以 jdk 為動態代理生成的 class 也提供了快取機制,下次使用的時候,直接透過反射建立 proxy 例項就可以,無需再重新生成 clazz 。

這裡的快取主要透過 java。lang。reflect。WeakCache 實現。

使用 key + sub-key 唯一確定一個 value。

其中,key 為傳入的 classLoader,sub-key 為透過 key + 傳入的介面 class 計算得出。

快取構成如下圖:

懶載入

起初 valuesMap 中快取的並不是最中的代理類,而是一個 ProxyClassFactory。

當呼叫 valueFactory。apply(key, parameter) 生成真正的代理類後,會將快取由 factory 物件更換為真正的代理。

if (!valuesMap。replace(subKey, this, cacheValue)) {

throw new AssertionError(“Should not reach here”);

}

完整地獲取快取流程如下:

上一篇:玻璃這麼不耐摔,為什麼手機廠商還是喜歡用玻璃後蓋?
下一篇:上升至海外銷量榜單第十隻用不到一年時間,OPPO是如何做到的?