Java 提供給了我們一個非常方便的動態代理類 Proxy
,讓我們今天來研究一下它的實現原理,以及爲什麼動態代理會存在性能問題。
個人博客:https://blog.N0tExpectErr0r.cn
小專欄:https://xiaozhuanlan.com/N0tExpectErr0r
代理對象的創建
我們往往通過 newProxyInstance(ClassLoader, Class<?>[], InvocationHandler)
方法進行代理對象的創建,它傳遞了三個參數:loader
、interfaces
以及 h
:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException {
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
// 通過 SercurityManager 進行對即將代理的類的 PackageAccess 的權限檢測
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
// 嘗試獲取或創建代理類
Class<?> cl = getProxyClass0(loader, intfs);
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
// 對非public類通過AccessController將Accessible設置爲true
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
// 以 InvocationHandler 作爲參數調用代理類的構造函數
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
這裏主要是如下的幾步:
- 通過
SecurityManager
對即將代理的類的 package access 進行檢測。 - 通過
getProxyClass0
嘗試獲取代理Class
類。 - 對非 public 的類,通過
AccessController
將Accessible
設置爲 true - 以
InvokeHandler
作爲參數調用代理類的構造函數構造對象。
顯然,動態代理的關鍵想必就是在 getProxyClass0
這個方法中了,可以大膽猜測一下這個代理類中的任何方法的調用都會通過傳遞進去的 InvokeHandler
進行代理。
我們看到 getProxyClass0
:
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// If the proxy class defined by the given loader implementing
// the given interfaces exists, this will simply return the cached copy;
// otherwise, it will create the proxy class via the ProxyClassFactory
return proxyClassCache.get(loader, interfaces);
}
很奇怪,這裏竟然只有一個從 proxyClassCache.get
的調用來獲取緩存中的代理 Class,剛開始看到這裏的時候以爲 proxyClassCache
是個 Map
導致非常困惑。之後看到了上面的註釋:
如果代理類已經被給定的
ClassLoader
實現過了,則從緩存中直接 copy 一份拿出,否則它會通過代理類的工廠ProxyClassFactory
進行創建
然後再仔細一看,原來 proxyClassCache
是一個 WeakCache
對象😂,那看來對代理 Class
對象的創建就在它的 get
方法中實現了。
WeakCache 緩存
數據結構
首先我們來了解一下 WeakCache
究竟是用來幹什麼的,我們先看到它內部的數據結構:
final class WeakCache<K, P, V> {
private final ReferenceQueue<K> refQueue
= new ReferenceQueue<>();
// the key type is Object for supporting null key
private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map
= new ConcurrentHashMap<>();
private final ConcurrentMap<Supplier<V>, Boolean> reverseMap
= new ConcurrentHashMap<>();
private final BiFunction<K, P, ?> subKeyFactory;
private final BiFunction<K, P, V> valueFactory;
}
可以看到,這裏有如下的幾個成員變量:
refQueue
:引用隊列。map
:一個ConcurrentHashMap
,裏面的value
也是ConcurrentHashMap
,它們的key
均爲Object
,用來支持爲 null 的key
,而value
則是Supplier
類。reverseMap
:一個key
爲Supplier
,value
爲Boolean
的ConcurrentHashMap
。subKeyFactory
:用於生成subKey
的工廠,也就是map
中的 Map 的key
。valueFactory
:用於生成value
對象的工廠,既然我們的Class
爲value
,這裏想必就是ProxyClassFactory
了。
果然,看到 proxyClassCache
的聲明處:
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
可以看到,它的 subKeyFactory
爲 KeyFactory
類,而 valueFactory
爲 ProxyClassFactory
。
get
我們接着看看它的 get
方法究竟做了什麼:
public V get(K key, P parameter) {
Objects.requireNonNull(parameter);
expungeStaleEntries();
// 通過 CacheKey.valueOf 將 key 轉換爲了存在緩存中的 CacheKey
Object cacheKey = CacheKey.valueOf(key, refQueue);
// 對 map 中的 value Map 進行獲取,採用了懶創建的思路,這裏還對併發問題進行了考慮
ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
if (valuesMap == null) {
ConcurrentMap<Object, Supplier<V>> oldValuesMap
= map.putIfAbsent(cacheKey,
valuesMap = new ConcurrentHashMap<>());
if (oldValuesMap != null) {
valuesMap = oldValuesMap;
}
}
// 通過 subKeyFactory 創建 subKey 並獲取對應的 Supplier
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
Supplier<V> supplier = valuesMap.get(subKey);
Factory factory = null;
while (true) {
// 通過 supplier.get 獲取對應的 value
if (supplier != null) {
// supplier might be a Factory or a CacheValue<V> instance
V value = supplier.get();
if (value != null) {
return value;
}
}
// 如果緩存中沒有對應的 supplier,可能是被 CacheValue 清理了,或者 Factory 還未 install
if (factory == null) {
factory = new Factory(key, parameter, subKey, valuesMap);
}
if (supplier == null) {
// 將 factory 放入 valuesMap 中,並將其設置爲 supplier
supplier = valuesMap.putIfAbsent(subKey, factory);
if (supplier == null) {
// successfully installed Factory
supplier = factory;
}
// 如果此時已經有 supplier,則用它繼續重試
} else {
if (valuesMap.replace(subKey, supplier, factory)) {
// Factory 成功 install,則將其設置爲 supplier 並重試
supplier = factory;
} else {
// retry with current supplier
supplier = valuesMap.get(subKey);
}
}
}
}
這裏看上去代碼比較長,其實結構是比較清晰的:
- 首先通過
CacheKey
將key
轉變爲在map
中的key
,這裏的key
實際上是我們的ClassLoader
,也就是這裏先用ClassLoader
進行了一遍篩選,是第一級緩存。 - 對
map
中的valuesMap
進行獲取,若還沒有則對其進行創建,這裏生成的就是我們的第二級緩存 Map。 - 通過
subKeyFactory
創建subKey
並獲取對應的Supplier
,通過自定義的subKeyFactory
進行創建,這個subKey
用於通過第二級緩存獲取真正的數據。 - 不斷循環,對
Supplier
進行創建並調用其get
方法獲取需要的value
。這裏創建Supplier
的具體邏輯感興趣的可以看看上面的代碼及註釋,最終創造出來的Supplier
是一個Factory
對象。
上面的代碼中可以發現考慮到了非常多的併發安全性問題,用到了很多 CAS 操作進行值的 put
,值得我們學習。
並且可以發現,WeakCache
中通過以 ClassLoader
爲 key
的第一級緩存,以及以自定義的 subKeyFactory
生成的 subKey
爲 key
的第二級緩存,極大地加快了 Map
的查找效率,也是很值得我們學習的一個操作。
那麼既然存在 valuesMap
中的 Supplier
是 Factory
對象,讓我們看看 Factory.get
方法:
@Override
public synchronized V get() { // serialize access
// 再次檢查當前的 Supplier 是不是自己
Supplier<V> supplier = valuesMap.get(subKey);
if (supplier != this) {
return null;
}
// 通過 valueFactory.apply 方法創建需要的對象
V value = null;
try {
value = Objects.requireNonNull(valueFactory.apply(key, parameter));
} finally {
if (value == null) { // remove us on failure
valuesMap.remove(subKey, this);
}
}
// ... 省略
return value;
}
可以看到,它實際上就是通過 valueFactory
進行了 value
的創建,並將其返回。
那麼我們對 WeakCache
類進行一下總結:
-
WeakCache
中只有get
方法,當緩存中沒有存在對應的value
時,它會通過創建時傳入的valueFactory
對value
進行創建。 -
它通過以
ClassLoader
爲key
的第一級緩存以及以自定義的subKeyFactory
生成的subKey
爲key
的第二級緩存,這樣的二級緩存機制極大的加快了我們對Map
的查找效率。 -
WeakCache
並沒有通過加鎖來保證線程安全,而是將它的線程安全交給了ConcurrentHashMap
來保證,並且通過大量的 CAS 操作來保證對Map
操作的安全性。
代理 Class 的創建
ProxyClassFactory
接着我們看看 ProxyClassFactory
是如何生成我們的代理類 Class
對象的:
// 代理類名的前綴
private static final String proxyClassNamePrefix = "$Proxy";
// 下一個用來生成代理類名的數
private static final AtomicLong nextUniqueNumber = new AtomicLong();
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
for (Class<?> intf : interfaces) {
// 遍歷接口列表通過 Class.forName 加載 Class 對象
Class<?> interfaceClass = null;
try {
interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
// ... 異常檢查
}
String proxyPkg = null; // package to define proxy class in
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
// 如果存在非 public 的接口,記錄非 public 接口的包名,後面會將它們生成的代理類採用同樣的 package access(這些接口必須來自同一個包)
for (Class<?> intf : interfaces) {
int flags = intf.getModifiers();
if (!Modifier.isPublic(flags)) {
// 構造
accessFlags = Modifier.FINAL;
String name = intf.getName();
int n = name.lastIndexOf('.');
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}
// 沒有非 public 的接口,用 sun.proxy 作爲其包名
if (proxyPkg == null) {
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
// 構造代理類的名字
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;
// 通過 ProxyGenerator.generateProxyClass 生成代理類 Class
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
// 通過 defineClass0 聲明 Class 並返回
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
/*
* A ClassFormatError here means that (barring bugs in the
* proxy class generation code) there was some other
* invalid aspect of the arguments supplied to the proxy
* class creation (such as virtual machine limitations
* exceeded).
*/
throw new IllegalArgumentException(e.toString());
}
}
可以看到,ProxyClassFactory
中主要是對代理類名的確定,它主要經歷了下面的步驟:
- 遍歷接口列表,通過
Class.forName
加載接口對應的Class
對象。 - 若存在非 public 的接口,用它們的包名作爲所有代理類的包名,並且它們的包訪問權限也會變成非 public。
- 若沒有非 public 的接口,使用
sun.proxy
作爲包名。 - 以
proxyPkg + proxyClassNamePrefix + num
的格式構建代理類名 - 調用
ProxyGenerator.generateProxyClass
創建代理Class
的 Class 文件。 - 通過
defineClass0
定義Class
並返回。
ProxyGenerator
顯然,Class
文件生成的關鍵位於ProxyGenerator.generateProxyClass
中,我們首先看到 ProxyGeneratory.generateProxyClass
。
我的 IDEA 只能查看到 Class 文件,如果想看 Java 文件的讀者可以到這裏查看:ProxyGenerator.java:
public static byte[] generateProxyClass(final String name,
Class<?>[] interfaces,
int accessFlags) {
ProxyGenerator gen = new ProxyGenerator(name, interfaces, accessFlags);
final byte[] classFile = gen.generateClassFile();
if (saveGeneratedFiles) {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() {
public Void run() {
try {
int i = name.lastIndexOf('.');
Path path;
if (i > 0) {
Path dir = Paths.get(name.substring(0, i).replace('.', File.separatorChar));
Files.createDirectories(dir);
path = dir.resolve(name.substring(i+1, name.length()) + ".class");
} else {
path = Paths.get(name + ".class");
}
Files.write(path, classFile);
return null;
} catch (IOException e) {
throw new InternalError(
"I/O exception saving generated file: " + e);
}
}
});
}
return classFile;
}
可以看到,它實際上是構建了一個對應參數的 ProxyGenerator
對象,並調用了其 generateClassFile
方法:
private byte[] generateClassFile() {
// 第一步,將類中的所有法組裝爲 ProxyMethod 對象
// 對於 hashCode equals toString 方法,首先調用 addProxyMethod 從而將原本的方法用 Object 的實現進行覆蓋
addProxyMethod(hashCodeMethod, Object.class);
addProxyMethod(equalsMethod, Object.class);
addProxyMethod(toStringMethod, Object.class);
// 對接口中其餘的方法調用 addProxyMethod 添加代理方法
for (Class<?> intf : interfaces) {
for (Method m : intf.getMethods()) {
addProxyMethod(m, intf);
}
}
// 遍歷所有生成的代理方法,確保它們的返回值是兼容的
for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
checkReturnTypes(sigmethods);
}
// 第二步,組裝生成的Class文件的FieldInfo和MethodInfo (每個方法都會生成一個static的Method對象和對應的代理方法)
try {
methods.add(generateConstructor());
for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
for (ProxyMethod pm : sigmethods) {
// 添加代理類的 static 字段
fields.add(new FieldInfo(pm.methodFieldName,
"Ljava/lang/reflect/Method;",
ACC_PRIVATE | ACC_STATIC));
// 添加代理方法
methods.add(pm.generateMethod());
}
}
methods.add(generateStaticInitializer());
} catch (IOException e) {
throw new InternalError("unexpected I/O Exception", e);
}
// 確保不會超過 65535 的限制
if (methods.size() > 65535) {
throw new IllegalArgumentException("method limit exceeded");
}
if (fields.size() > 65535) {
throw new IllegalArgumentException("field limit exceeded");
}
// 第三步,寫入 Class 文件
// 對常量池進行驗證
cp.getClass(dotToSlash(className));
cp.getClass(superclassName);
for (Class<?> intf: interfaces) {
cp.getClass(dotToSlash(intf.getName()));
}
// 常量池設置爲已讀
cp.setReadOnly();
ByteArrayOutputStream bout = new ByteArrayOutputStream();
DataOutputStream dout = new DataOutputStream(bout);
try {
// 寫入MagicNumber
dout.writeInt(0xCAFEBABE);
// 寫入minor版本號
dout.writeShort(CLASSFILE_MINOR_VERSION);
// 寫入major版本號
dout.writeShort(CLASSFILE_MAJOR_VERSION);
// 寫入常量池
cp.write(dout);
// 寫入 accessFlags
dout.writeShort(accessFlags);
// 寫入當前class索引
dout.writeShort(cp.getClass(dotToSlash(className)));
// 寫入父類/接口索引
dout.writeShort(cp.getClass(superclassName));
// 寫入接口個數
dout.writeShort(interfaces.length);
// 寫入接口
for (Class<?> intf : interfaces) {
dout.writeShort(cp.getClass(
dotToSlash(intf.getName())));
}
// 寫入字段個數
dout.writeShort(fields.size());
// 寫入字段
for (FieldInfo f : fields) {
f.write(dout);
}
// 寫入方法個數
dout.writeShort(methods.size());
// 寫入方法
for (MethodInfo m : methods) {
m.write(dout);
}
// 寫入屬性表,代理類不需要屬性表(包含了代碼)
dout.writeShort(0);
} catch (IOException e) {
throw new InternalError("unexpected I/O Exception", e);
}
// 輸出爲byte數組
return bout.toByteArray();
}
這裏的代碼非常非常非常長,但是結構其實非常清晰,並且官方几乎對每個操作都加上了註釋,非常易於理解:
- 第一步,將類中的所有法組裝爲
ProxyMethod
對象- 對於
hashCode
、equals
、toString
方法,首先調用addProxyMethod
從而將原本的方法用Object
的實現進行覆蓋 - 對接口中其餘的方法調用
addProxyMethod
添加代理方法。 - 遍歷所有生成的代理方法,確保它們的返回值是兼容的。
- 對於
- 第二步,組裝生成的
Class
文件的FieldInfo
和MethodInfo
(每個方法都會生成一個static
的Method
成員變量和對應的代理方法)- 遍歷所有
ProxyMethod
,添加代理類的static Method
對象字段,並添加對應的代理方法。 - 確保方法數、字段數不會超過 65535 的限制。
- 遍歷所有
- 第三步,將所有內容寫入
Class
文件(之前寫ClassDecoder
的時候寫了很多類似的代碼,很有感觸哈哈,又複習了一遍Class
文件的結構)- 寫入
MagicNumber
- 寫入
minor
版本號 - 寫入
major
版本號 - 寫入常量池
- 寫入
accessFlags
- 寫入當前類的索引
- 寫入父類/接口的索引
- 寫入接口個數
- 寫入接口
- 寫入字段個數
- 寫入字段
- 寫入方法個數
- 寫入方法
- 寫入屬性表,由於代理類不需要屬性表,因此寫入的是 0
- 輸出爲
byte
數組。
- 寫入
關於 ProxyGenerator
的 addProxyMethod
等具體實現我們就不再關注了,實際上就是通過反射解析方法的參數等信息然後創建一個對應的方法。最後生成的代理類中的每個方法都會調用到 InvokeHandler.invoke
方法。
而最後的 defineClass0
方法是一個 native 方法,估計是調用到了 JVM 底層的方法,這裏就不去關注了。
總結
JDK 中的動態代理是一套基於反射獲取接口的信息,通過對 Class 文件按字節寫入生成新的反射類文件並加載進 JVM 的動態代理框架。它有個很致命的特點就是只支持對接口的代理
一個代理對象的生成主要分兩步:
-
通過
WeakCache
獲取緩存的Class
對象。其中WeakCache
存在如下的特點:-
WeakCache
中只有get
方法,當緩存中沒有存在對應的value
時,它會通過創建時傳入的valueFactory
對value
進行創建。 -
它通過以
ClassLoader
爲key
的第一級緩存以及以自定義的subKeyFactory
生成的subKey
爲key
的第二級緩存,這樣的二級緩存機制極大的加快了我們對Map
的查找效率。 -
WeakCache
並沒有通過加鎖來保證線程安全,而是將它的線程安全交給了ConcurrentHashMap
來保證,並且通過大量的 CAS 操作來保證對Map
操作的安全性。
-
-
創建接口對應的
Class
代理類對象。- 通過
ProxyClassFactory
先解析接口的信息並生成對應的代理類名。 - 通過
ProxyGenerator
對類文件的信息進行組裝並輸出對應的Class
文件,主要分爲以下三步:- 第一步,將類中的所有法組裝爲
ProxyMethod
對象。 - 第二步,組裝生成的
Class
文件的FieldInfo
和MethodInfo
。 - 第三步,將所有內容寫入
Class
文件
- 第一步,將類中的所有法組裝爲
- 通過
最終,代理類中所有的方法的調用都會調用到 InvocationHandler.invoke
,從而實現用戶對接口的代理。