淺析JDK動態代理的源碼

        最近查看了一下JDK實現動態代理的部分源碼,這裏作一個簡單的記錄。java

1. JDK 動態代理的使用

       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

2. 淺析JDK 動態代理的源碼

      上面簡單介紹了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了!說明以前的猜測基本獲得了驗證。

3. JDK如何構造一個代理類

      在上一個部分中咱們知道在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這個方法的實現我尚未細看,由於太長了,細節也比較多,但願等之後看完了再來更新這篇博客。

 

4. 總結

       JDK實現動態代理的過程以下:

  1. 克隆傳入的接口Class數組
  2. 查找或者生成實現了接口數組中全部接口的動態代理類的Class
  3. 利用動態代理類的Class獲取Constructor對象
  4. Constructor對象利用咱們傳入的InvocationHandler實現類對象做爲輸入參數,生成動態代理類的對象

       JDK生成的動態代理類對象的一些性質:

  1. 實現了咱們傳入的接口Class數組中全部的接口
  2. 這些接口中若是含有非public的接口,則左右非public接口必須在同一個包下面
  3. 父類有一個InvocationHandler的成員變量,它的值是咱們在Proxy.newInstance()方法中傳入的,因此它和InvocationHandler的關係不是is-a,而是has-a
相關文章
相關標籤/搜索