JDK動態代理(2)JDK動態代理的底層實現之Proxy原始碼分析
JDK動態代理(2)JDK動態代理的底層實現之Proxy原始碼分析
在上一篇裡為大家簡單介紹了什麼是代理模式?為什麼要使用代理模式?並用例子演示了一下靜態代理和動態代理的實現,分析了靜態代理和動態代理各自的優缺點。在這一篇中筆者打算深入原始碼為大家剖析JDK動態代理實現的機制,建議讀者閱讀本篇前可先閱讀一下筆者上一篇關於代理模式的介紹《JDK動態代理[1]----代理模式實現方式的概要介紹》
上一篇動態代理的測試類中使用了Proxy類的靜態方法newProxyInstance方法去生成一個代理類,這個靜態方法接收三個引數,分別是目標類的類載入器,目標類實現的介面集合,InvocationHandler例項,最後返回一個Object型別的代理類。我們先從該方法開始,看看代理類是怎樣一步一步造出來的,廢話不多說,直接上程式碼
newProxyInstance方法:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) throws IllegalArgumentException {
//驗證傳入的InvocationHandler不能為空
Objects.requireNonNull (h);
//複製代理類實現的所有介面
final Class<?>[] intfs = interfaces.clone();
//獲取安全管理器
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);
}
//獲取引數型別是InvocationHandler.class的代理類構造器
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
//如果代理類是不可訪問的, 就使用特權將它的構造器設定為可訪問
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
//傳入InvocationHandler例項去構造一個代理類的例項
//所有代理類都繼承自Proxy, 因此這裡會呼叫Proxy的構造器將InvocationHandler引用傳入
return cons.newInstance(new Object[]{h});
} catch (Exception e) {
//為了節省篇幅, 筆者統一用Exception捕獲了所有異常
throw new InternalError(e.toString(), e);
}
}
可以看到,newProxyInstance方法首先是對引數進行一些許可權校驗,之後通過呼叫getProxyClass0方法生成了代理類的類物件,然後獲取引數型別是InvocationHandler.class的代理類構造器。檢驗構造器是否可以訪問,最後傳入InvocationHandler例項的引用去構造出一個代理類例項,InvocationHandler例項的引用其實是Proxy持有著,因為生成的代理類預設繼承自Proxy,所以最後會呼叫Proxy的構造器將引用傳入。在這裡我們重點關注getProxyClass0這個方法,看看代理類的Class物件是怎樣來的,下面貼上該方法的程式碼
getProxyClass0方法:
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
//目標類實現的介面不能大於65535
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
//獲取代理類使用了快取機制
return proxyClassCache.get(loader, interfaces);
}
可以看到getProxyClass0方法內部沒有多少內容,首先是檢查目標代理類實現的介面不能大於65535這個數,之後是通過類載入器和介面集合去快取裡面獲取,如果能找到代理類就直接返回,否則就會呼叫ProxyClassFactory這個工廠去生成一個代理類。關於這裡使用到的快取機制我們留到下一篇專門介紹,首先我們先看看這個工廠類是怎樣生成代理類的。
ProxyClassFactory工廠類:
//代理類生成工廠
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], 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) {
//這裡遍歷interfaces陣列進行驗證, 主要做三件事情
//1.intf是否可以由指定的類載入進行載入
//2.intf是否是一個介面
//3.intf在陣列中是否有重複
}
//生成代理類的包名
String proxyPkg = null;
//生成代理類的訪問標誌, 預設是public final的
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
for (Class<?> intf : interfaces) {
//獲取介面的訪問標誌
int flags = intf.getModifiers();
//如果介面的訪問標誌不是public, 那麼生成代理類的包名和介面包名相同
if (!Modifier.isPublic(flags)) {
//生成的代理類的訪問標誌設定為final
accessFlags = Modifier.FINAL;
//獲取介面全限定名, 例如:java.util.Collection
String name = intf.getName();
int n = name.lastIndexOf('.');
//剪裁後得到包名:java.util
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
//生成的代理類的包名和介面包名是一樣的
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
//代理類如果實現不同包的介面, 並且介面都不是public的, 那麼就會在這裡報錯
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}
//如果介面訪問標誌都是public的話, 那生成的代理類都放到預設的包下:com.sun.proxy
if (proxyPkg == null) {
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
//生成代理類的序號
long num = nextUniqueNumber.getAndIncrement();
//生成代理類的全限定名, 包名+字首+序號, 例如:com.sun.proxy.$Proxy0
String proxyName = proxyPkg + proxyClassNamePrefix + num;
//這裡是核心, 用ProxyGenerator來生成位元組碼, 該類放在sun.misc包下
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName,
interfaces, accessFlags);
try {
//根據二進位制檔案生成相應的Class例項
return defineClass0(loader, proxyName, proxyClassFile,
0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}
}
該工廠的apply方法會被呼叫用來生成代理類的Class物件,由於程式碼的註釋比較詳細,我們只挑關鍵點進行闡述,其他的就不反覆贅述了。
-
在程式碼中可以看到JDK生成的代理類的類名是“$Proxy”+序號。
-
如果介面是public的,代理類預設是public final的,並且生成的代理類預設放到com.sun.proxy這個包下。
-
如果介面是非public的,那麼代理類也是非public的,並且生成的代理類會放在對應介面所在的包下。
-
如果介面是非public的,並且這些介面不在同一個包下,那麼就會報錯。
生成具體的位元組碼是呼叫了ProxyGenerator這個類的generateProxyClass方法。這個類放在sun.misc包下,後續我們會扒出這個類繼續深究其底層原始碼。到這裡我們已經分析了Proxy這個類是怎樣生成代理類物件的,通過原始碼我們更直觀的瞭解了整個的執行過程,包括代理類的類名是怎樣生成的,代理類的訪問標誌是怎樣確定的,生成的代理類會放到哪個包下面,以及InvocationHandler例項的引用是怎樣傳入的。不過讀者可能還會有疑問,WeakCache快取是怎樣實現的?為什麼proxyClassCache.get(loader, interfaces)最後會呼叫到ProxyClassFactory工廠的apply方法?在下一篇中將會為讀者詳細介紹WeakCache快取的實現原理。