1. 程式人生 > >JDK動態代理[2]----JDK動態代理的底層實現之Proxy源碼分析

JDK動態代理[2]----JDK動態代理的底層實現之Proxy源碼分析

sco 不可 -- 例如 mis tfs err eno entity

在上一篇裏為大家簡單介紹了什麽是代理模式?為什麽要使用代理模式?並用例子演示了一下靜態代理和動態代理的實現,分析了靜態代理和動態代理各自的優缺點。在這一篇中筆者打算深入源碼為大家剖析JDK動態代理實現的機制,建議讀者閱讀本篇前可先閱讀一下筆者上一篇關於代理模式的介紹《JDK動態代理[1]----代理模式實現方式的概要介紹》

上一篇動態代理的測試類中使用了Proxy類的靜態方法newProxyInstance方法去生成一個代理類,這個靜態方法接收三個參數,分別是目標類的類加載器,目標類實現的接口集合,InvocationHandler實例,最後返回一個Object類型的代理類。我們先從該方法開始,看看代理類是怎樣一步一步造出來的,廢話不多說,直接上代碼

newProxyInstance方法:

 1 public static Object newProxyInstance(ClassLoader loader,
 2                                       Class<?>[] interfaces,
 3                                       InvocationHandler h) throws IllegalArgumentException {
 4     //驗證傳入的InvocationHandler不能為空
 5     Objects.requireNonNull(h);
6 //復制代理類實現的所有接口 7 final Class<?>[] intfs = interfaces.clone(); 8 //獲取安全管理器 9 final SecurityManager sm = System.getSecurityManager(); 10 //進行一些權限檢驗 11 if (sm != null) { 12 checkProxyAccess(Reflection.getCallerClass(), loader, intfs); 13 } 14 //該方法先從緩存獲取代理類, 如果沒有再去生成一個代理類
15 Class<?> cl = getProxyClass0(loader, intfs); 16 try { 17 //進行一些權限檢驗 18 if (sm != null) { 19 checkNewProxyPermission(Reflection.getCallerClass(), cl); 20 } 21 //獲取參數類型是InvocationHandler.class的代理類構造器 22 final Constructor<?> cons = cl.getConstructor(constructorParams); 23 final InvocationHandler ih = h; 24 //如果代理類是不可訪問的, 就使用特權將它的構造器設置為可訪問 25 if (!Modifier.isPublic(cl.getModifiers())) { 26 AccessController.doPrivileged(new PrivilegedAction<Void>() { 27 public Void run() { 28 cons.setAccessible(true); 29 return null; 30 } 31 }); 32 } 33 //傳入InvocationHandler實例去構造一個代理類的實例 34 //所有代理類都繼承自Proxy, 因此這裏會調用Proxy的構造器將InvocationHandler引用傳入 35 return cons.newInstance(new Object[]{h}); 36 } catch (Exception e) { 37 //為了節省篇幅, 筆者統一用Exception捕獲了所有異常 38 throw new InternalError(e.toString(), e); 39 } 40 }

可以看到,newProxyInstance方法首先是對參數進行一些權限校驗,之後通過調用getProxyClass0方法生成了代理類的類對象,然後獲取參數類型是InvocationHandler.class的代理類構造器。檢驗構造器是否可以訪問,最後傳入InvocationHandler實例的引用去構造出一個代理類實例,InvocationHandler實例的引用其實是Proxy持有著,因為生成的代理類默認繼承自Proxy,所以最後會調用Proxy的構造器將引用傳入。在這裏我們重點關註getProxyClass0這個方法,看看代理類的Class對象是怎樣來的,下面貼上該方法的代碼

getProxyClass0方法:

1 private static Class<?> getProxyClass0(ClassLoader loader,
2                                        Class<?>... interfaces) {
3     //目標類實現的接口不能大於65535
4     if (interfaces.length > 65535) {
5         throw new IllegalArgumentException("interface limit exceeded");
6     }
7     //獲取代理類使用了緩存機制
8     return proxyClassCache.get(loader, interfaces);
9 }

可以看到getProxyClass0方法內部沒有多少內容,首先是檢查目標代理類實現的接口不能大於65535這個數,之後是通過類加載器和接口集合去緩存裏面獲取,如果能找到代理類就直接返回,否則就會調用ProxyClassFactory這個工廠去生成一個代理類。關於這裏使用到的緩存機制我們留到下一篇專門介紹,首先我們先看看這個工廠類是怎樣生成代理類的。

ProxyClassFactory工廠類:

 1 //代理類生成工廠
 2 private static final class ProxyClassFactory 
 3                 implements BiFunction<ClassLoader, Class<?>[], Class<?>> {
 4     //代理類名稱前綴
 5     private static final String proxyClassNamePrefix = "$Proxy";
 6     //用原子類來生成代理類的序號, 以此來確定唯一的代理類
 7     private static final AtomicLong nextUniqueNumber = new AtomicLong();
 8     @Override
 9     public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
10         Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
11         for (Class<?> intf : interfaces) {
12             //這裏遍歷interfaces數組進行驗證, 主要做三件事情
13             //1.intf是否可以由指定的類加載進行加載
14             //2.intf是否是一個接口
15             //3.intf在數組中是否有重復
16         }
17         //生成代理類的包名
18         String proxyPkg = null;
19         //生成代理類的訪問標誌, 默認是public final的
20         int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
21         for (Class<?> intf : interfaces) {
22             //獲取接口的訪問標誌
23             int flags = intf.getModifiers();
24             //如果接口的訪問標誌不是public, 那麽生成代理類的包名和接口包名相同
25             if (!Modifier.isPublic(flags)) {
26                 //生成的代理類的訪問標誌設置為final
27                 accessFlags = Modifier.FINAL;
28                 //獲取接口全限定名, 例如:java.util.Collection
29                 String name = intf.getName();
30                 int n = name.lastIndexOf(‘.‘);
31                 //剪裁後得到包名:java.util
32                 String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
33                 //生成的代理類的包名和接口包名是一樣的
34                 if (proxyPkg == null) {
35                     proxyPkg = pkg;
36                 } else if (!pkg.equals(proxyPkg)) {
37                     //代理類如果實現不同包的接口, 並且接口都不是public的, 那麽就會在這裏報錯
38                     throw new IllegalArgumentException(
39                         "non-public interfaces from different packages");
40                 }
41             }
42         }
43         //如果接口訪問標誌都是public的話, 那生成的代理類都放到默認的包下:com.sun.proxy
44         if (proxyPkg == null) {
45             proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
46         }
47         //生成代理類的序號
48         long num = nextUniqueNumber.getAndIncrement();
49         //生成代理類的全限定名, 包名+前綴+序號, 例如:com.sun.proxy.$Proxy0
50         String proxyName = proxyPkg + proxyClassNamePrefix + num;
51         //這裏是核心, 用ProxyGenerator來生成字節碼, 該類放在sun.misc包下
52         byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName,
53                                   interfaces, accessFlags);
54         try {
55             //根據二進制文件生成相應的Class實例
56             return defineClass0(loader, proxyName, proxyClassFile, 
57                               0, proxyClassFile.length);
58         } catch (ClassFormatError e) {
59             throw new IllegalArgumentException(e.toString());
60         }
61     }
62 }

該工廠的apply方法會被調用用來生成代理類的Class對象,由於代碼的註釋比較詳細,我們只挑關鍵點進行闡述,其他的就不反復贅述了。

1. 在代碼中可以看到JDK生成的代理類的類名是“$Proxy”+序號。

2. 如果接口是public的,代理類默認是public final的,並且生成的代理類默認放到com.sun.proxy這個包下。

3. 如果接口是非public的,那麽代理類也是非public的,並且生成的代理類會放在對應接口所在的包下。

4. 如果接口是非public的,並且這些接口不在同一個包下,那麽就會報錯。

生成具體的字節碼是調用了ProxyGenerator這個類的generateProxyClass方法。這個類放在sun.misc包下,後續我們會扒出這個類繼續深究其底層源碼。到這裏我們已經分析了Proxy這個類是怎樣生成代理類對象的,通過源碼我們更直觀的了解了整個的執行過程,包括代理類的類名是怎樣生成的,代理類的訪問標誌是怎樣確定的,生成的代理類會放到哪個包下面,以及InvocationHandler實例的引用是怎樣傳入的。不過讀者可能還會有疑問,WeakCache緩存是怎樣實現的?為什麽proxyClassCache.get(loader, interfaces)最後會調用到ProxyClassFactory工廠的apply方法?在下一篇中將會為讀者詳細介紹WeakCache緩存的實現原理。

JDK動態代理[2]----JDK動態代理的底層實現之Proxy源碼分析