Java基礎-瞭解一下jdk的動態代理的本質

吾乃上將軍邢道榮發表於2019-03-10

先簡短的回顧下jdk動態代理用法

1.定義一個基礎的介面

public interface Service {
    void print();
}
複製程式碼

2.簡單的實現一下介面

public class MyService implements Service {
    @Override
    public void print() {
        System.out.println("this is print");
    }
}
複製程式碼

3.實現jdk的InvocationHandler介面

public class MyHandler implements InvocationHandler {

    private Service service;

    public MyHandler(Service service){
        this.service = service;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("this is before!");
        Object result = method.invoke(service, args);
        System.out.println("this is after!");
        return result;
    }
}
複製程式碼

4.呼叫Proxy類實現動態增強

public static void main(String[] args) {
        Service service = new MyService();
        Service proxyInstance = (Service) Proxy.newProxyInstance(Demo.class.getClassLoader(), new Class[]{Service.class}, new MyHandler(service));
        proxyInstance.print();
    }
複製程式碼

如果不出意外的話,控制檯會列印出如下資訊

this is before!

this is print

this is after!

這說明我們寫的方法,得到了增強!

JDK動態代理的原理

1.jdk動態代理的實質是什麼?

不知道大家有沒有想過,這行程式碼

Proxy.newProxyInstance(Demo.class.getClassLoader(), new Class[]{Service.class}, new MyHandler(service));
複製程式碼

返回的究竟是個什麼東西?

帶著疑問,我們先使用反射列印一下類名試試。

System.out.println(proxyInstance.getClass().getName());
複製程式碼

得到的結果為

com.sun.proxy.$Proxy0
複製程式碼

很顯然這個類並不是我們建立的。所以到這兒就應該想到了,動態代理實質,就是使用位元組碼技術,重新生成了一個新類,來達到增強的效果。

那麼增強的新類到底是個怎樣的類呢?我們來挖掘一下動態代理的原始碼。

2.jdk動態代理原始碼分析

分析原始碼的時候我一般都是根據方法的引數和返回值,大致推敲一下方法的功能,這樣可以快速找到關鍵方法。所以後面的程式碼都是被我精簡過的,大家可以對比著原始碼閱讀。

首先進入newProxyInstance方法,在去除掉業務不相干程式碼後如下:

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
                                          
            Class<?> cl = getProxyClass0(loader, intfs);
            
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            
            return cons.newInstance(new Object[]{h});
    }
複製程式碼
  1. 第一步通過getProxyClass0方法,獲得了一個class物件cl
  2. 第二步獲得了cl物件的建構函式,這個建構函式的引數是一個 InvocationHandler型別
  3. 第三步通過我們傳入的InvocationHandler介面實現類h構造了cl物件的例項。

也就是新類就算這個cl,弄清除cl是啥,動態代理的原理我們就基本弄懂了。所以跟著這個目標,進入getProxyClass0方法。

 private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        return proxyClassCache.get(loader, interfaces);
}
複製程式碼

除去校驗的方法外,只剩下一行程式碼。所以只能在進去看看。在進入之前,先看看proxyClassCache是啥。

    private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
        
    public WeakCache(BiFunction<K, P, ?> subKeyFactory,
                     BiFunction<K, P, V> valueFactory) {
        this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
        this.valueFactory = Objects.requireNonNull(valueFactory);
    }
複製程式碼

現在在進入proxyClassCache的get方法裡,進入get方法咋一看程式碼有點多,其實上面都是快取相關處理(快取的話我建議等看完主流程再回頭看,那樣更有助於理解快取),我們先跳過,直接看關鍵程式碼

while (true) {
            if (supplier != null) {
                V value = supplier.get();
                if (value != null) {
                    return value;
                }
            }
            if (factory == null) {
                factory = new Factory(key, parameter, subKey, valuesMap);
            }

            if (supplier == null) {
                supplier = valuesMap.putIfAbsent(subKey, factory);
                if (supplier == null) {
                    supplier = factory;
                }
            } 
        }
複製程式碼

關鍵程式碼經簡化後如上,這樣就能清晰的看出supplier就是new Factory(key, parameter, subKey, valuesMap)的例項。所以進入supplier的get方法。

public synchronized V get() { // serialize access
            try {
                value = Objects.requireNonNull(valueFactory.apply(key, parameter));
            } finally {
                if (value == null) { // remove us on failure
                    valuesMap.remove(subKey, this);
                }
            }
            return value;
        }
    }
複製程式碼

進入get方法後,關鍵程式碼就一句valueFactory.apply(key, parameter),而這個valueFactory就是上面在建立WeakCache時設定的ProxyClassFactory。所以進入ProxyClassFactory的apply方法。

在apply方法中,終於看到了建立代理類的關鍵方法

       byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
       proxyName, interfaces, accessFlags);
       return defineClass0(loader, proxyName,
                            proxyClassFile, 0, proxyClassFile.length);
複製程式碼

其中proxyClassFile就是新類的位元組碼,而defineClass0方法,就是載入這個新類。對於位元組碼如何構造在這兒我就不深究了。感興趣的可以自己研究。我更關心新類的結構。所以既然找到了新類的生成方法,我們就將他列印到檔案中瞧一瞧。

        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                "ProxyService", new Class[]{Service.class}, Modifier.FINAL);
        OutputStream outputStream = new FileOutputStream(new File("d:/JdkProxy.class"));
        outputStream.write(proxyClassFile);
複製程式碼

在d盤下找到這個檔案,直接用idea開啟。

final class ProxyService extends Proxy implements Service {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public ProxyService(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void print() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("study.demo.jdk_proxy.Service").getMethod("print");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}
複製程式碼

不出意外就會看到如上程式碼了。其實看到這個程式碼後,jdk動態代理的本質心裡就應該有底了。所以後面就不用再說下去了吧。

下一篇,分析一下cglib動態代理的本質

相關文章