最近查看了一下JDK實現動態代理的部分源碼,這裏作一個簡單的記錄。java
JDK爲咱們實現了動態代理,它是基於接口的實現,也就是說要爲某個類動態地生成一個代理類的話,這個類必需要實現一個或多個接口,若是沒有實現接口,JDK動態代理就無能爲力了,只能使用CGlib來實現動態代理。首先看一個簡單使用的例子:數組
// 定義一個接口 interface SayHello { public void print(String msg); } // 實現一個接口,HelloImpl就是咱們要生成動態代理的類 class HelloImpl implements SayHello { @Override public void print(String msg) { System.out.println(msg); } }
// 定義一個實現了InvocationHandler接口的類 class DynamicProxy implements InvocationHandler { private Object target; public DynamicProxy(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 調用目標方法前須要作的操做,例如打印日誌 System.out.println("~~~ Before invoking Log ~~~"); System.out.println("\t" + proxy.getClass().getCanonicalName()); System.out.println("\t" + method.getName()); // 真正地調用想要實際須要調用的方法 Object result = method.invoke(target, args); // 調用目標方法以後須要作的操做,例如輸出日誌等 System.out.println("~~~ After invoking Log ~~~"); // 返回方法執行的結果 return result; } }
public class ProxyTest { public static void main(String[] args) { SayHello hello = new HelloImpl(); SayHello proxyHello = (SayHello) Proxy.newProxyInstance( hello.getClass().getClassLoader(), hello.getClass().getInterfaces(), new DynamicProxy(hello)); proxyHello.print("Hey! I'm Joey~"); } }
程序的輸出以下app
~~~ Before invoking Log ~~~ $Proxy0 print Hey! I'm Joey~ ~~~ After invoking Log ~~~
從上面的輸出能夠看到,咱們實現了AOP的效果,在print方法調用先後都打印了日誌的輸出。須要注意的是,在InvocationHandler的invoke()方法中,第一個參數Object proxy實際上是動態生成的代理對象,從輸出結果也能看到名字是$Proxy0,而不是原來的HelloImpl對象,原來的HelloImpl對象的名字應該是HelloImpl纔對,因此咱們在調用真正的print方法時要傳入原來的HelloImpl對象,即ide
method.invoke(target, args)
若是使用下面的調用則會產生無線循環的異常:函數
method.invoke(proxy, args)
這是一個很tricky的bug,由於proxy是代理對象,它也有method,也就是咱們這裏的print方法,原本代理對象在執行print方法的時候就是調用InvocationHandler的invoke方法去間接地執行print方法的,那麼在invoke方法中又執行method方法(即print方法),它又會調用InvocationHandler的invoke方法,這樣就是無限遞歸永遠出不來了,因此咱們在初始化DynamicProxy的時候要傳入一個對象做爲真正要調用方法的原生對象,而後在真正須要調用這個方法時候使用method.invoke(target, args)語句來調用。ui
上面簡單介紹了JDK動態代理的使用,若是用過的小夥伴們應該都不須要更多解釋了,下面進入正題,淺析JDK實現動態代理的源碼。首先看一下Proxy的newInstance方法的簽名:this
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
這3個傳入參數分別是ClassLoader,要實現的接口數組,實現了InvocationHandler接口的類,查看一下newInstance()方法的源碼(部分):spa
final Class<?>[] intfs = interfaces.clone(); final SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkProxyAccess(Reflection.getCallerClass(), loader, intfs); } /* * Look up or generate the designated proxy class. */ Class<?> cl = getProxyClass0(loader, intfs);
首先克隆一個接口數組,暫時還不清楚爲何要這樣作,若是有知道的懇請賜教。接着作一些檢查,最後根據ClassLoader和接口數組獲得代理類,getProxyClass0()方法會查找一個cache中是否已經保存了這樣一個代理類,若是有就直接返回,不然生成一個。至因而如何產生這樣一個代理類,請看下一個部分,省得棧太深,最後都忘了要幹嗎了.....再看接下來的代碼:代理
try { final Constructor<?> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) { // create proxy instance with doPrivilege as the proxy class may // implement non-public interfaces that requires a special permission return AccessController.doPrivileged(new PrivilegedAction<Object>() { public Object run() { return newInstance(cons, ih); } }); } else { return newInstance(cons, ih); } } catch (NoSuchMethodException e) { throw new InternalError(e.toString()); }
cl是以前獲取到的代理類的Class,根據參數獲得構造函數,constructorParams其實就是{InvocationHandler.class}數組,也就是說生成的代理類含有一個構造函數,這個構造函數須要接收一個InvocationHandler的參數。最後把Constructor和InvocationHandler傳給newInstance方法,這個方法也很簡單,就是讓Constructor利用InvocationHandler參數生成一個對象,最後把這個對象返回,也就是上面例子中的proxyHello對象。這個對象實現了接口數組中所有的接口,所以能夠轉型爲SayHello類型。利用下面的代碼能夠獲得proxyHello實現的全部接口:日誌
Class<?>[] interfaces = proxyHello.getClass().getInterfaces(); for(Class<?> face : interfaces) { System.out.println(face.getCanonicalName()); }
輸出結果爲: SayHello
一開始我看到這個結果很奇怪,我覺得JDK除了實現傳入的接口數組裏的接口之外,也會實現InvocationHandler接口,而後在invoke方法中調用咱們傳給它的InvocationHandler實現類對象(即DynamicProxy)去真正地調用invoke方法,就如同咱們實現靜態代理那樣作。那麼既然動態生成的代理類沒有實現InvocationHandler接口,它的構造函數又須要接收一個InvocationHandler對象的目的是什麼呢?個人猜想是把傳入的對象做爲它的成員變量,而後在咱們調用接口裏的方法的時候就用這個成員對象去調用invoke方法。利用以下的語句查看動態代理類的成員變量:
Field[] declaredFields = proxyHello.getClass().getDeclaredFields(); for(Field field : declaredFields) { field.setAccessible(true); System.out.println(field.getType().getName()); }
打印的結果竟然全是java.lang.reflect.Method,那麼咱們傳給它的DynamicProxy對象去了哪裏呢?估計是在父類裏吧,換個語句:
Field[] declaredFields = proxyHello.getClass().getSuperclass().getDeclaredFields();
如今打印的結果以下:
long [Ljava.lang.Class; java.lang.reflect.WeakCache java.lang.reflect.InvocationHandler java.lang.Object
終於看到我傳入的InvocationHandler了!說明以前的猜測基本獲得了驗證。
在上一個部分中咱們知道在getProxyClass0方法中的代碼會從一個cache中查找或者生成一個代理類對象,這個方法的代碼以下:
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);
首先這個存放接口Class的數組長度不能超過65535,至於爲何是這個數字我也不知道。。而後就是利用get方法來獲取代理類對象,這個方法的代碼我也查看了,不過沒太看懂,隱約感受就是用一些工廠來生產cache中不存在的對象。看不懂暫時不要緊,還好這裏JDK的註釋也說得很清楚了,若是不存在的話就經過ProxyClassFactory來創造代理類,在get方法中也確實是調用了ProxyClassFactory的apply方法去生產對象(還好這一步看懂了,汗),那麼就直奔重點吧,ProxyClassFactory是java.lang.reflect.Proxy類的一個private static final class,它的apply方法簽名以下:
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces)
從頭至尾來看一下代碼(一些不過重要的就暫時過濾):
for (Class<?> intf : interfaces) { ...... // 這裏判斷數組裏的每個Class是否是接口,不是接口的話拋出異常,因此說JDK的動態代理只能實現接口 /* * Verify that the Class object actually represents an * interface. */ if (!interfaceClass.isInterface()) { throw new IllegalArgumentException( interfaceClass.getName() + " is not an interface"); } ...... }
// 定義生成的動態代理類所在包的包名 String proxyPkg = null; /* * Record the package of a non-public proxy interface so that the * proxy class will be defined in the same package. Verify that * all non-public proxy interfaces are in the same package. */ for (Class<?> intf : interfaces) { int flags = intf.getModifiers(); if (!Modifier.isPublic(flags)) { 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"); } } }
這裏的代碼和註釋都說得很清楚了,若是接口Class數組中有的接口的訪問屬性不是public的,例如是包訪問權限的,那麼全部這些接口都必須在一個包裏面,不然的話要拋出異常。之因此這麼作,理由也很簡單,舉個例子,你本身定義一個類Test去實現這些接口,若是有兩個接口不是public的,並且它們分別放在了不一樣的包裏面,那麼這個Test類不管放在哪一個包下面都必然會致使有一個接口是無權訪問的,所以若是有的接口不是public的,那麼它們必須在一個包下面,同時實現類也必須跟它們在同一個包下,這樣才能夠訪問嘛。
if (proxyPkg == null) { // if no non-public proxy interfaces, use com.sun.proxy package proxyPkg = ReflectUtil.PROXY_PACKAGE + "."; } /* * Choose a name for the proxy class to generate. */ long num = nextUniqueNumber.getAndIncrement(); String proxyName = proxyPkg + proxyClassNamePrefix + num;
這裏就是給生成的動態代理類取名,全部代理類的名字都是$Proxy開頭、而且後面跟一個數字的形式,另外,若是沒有非public的接口,那麼就把生成的代理類放在com.sun.proxy包下面。
byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces); return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
這一步最關鍵,就是傳入代理類的名字和它要實現的接口,從而生成class文件並存放到byte數組中,最後調用defineClass0這個native方法去定義這個動態代理類的Class對象。關於generateProxyClass這個方法的實現我尚未細看,由於太長了,細節也比較多,但願等之後看完了再來更新這篇博客。
JDK實現動態代理的過程以下:
JDK生成的動態代理類對象的一些性質: