JDK動態(tài)代理的實現(xiàn)及原理 作者:二青 郵箱:xtfggef@gmail.com 微博:http://weibo.com/xtfggef 動態(tài)代理,聽上去很高大上的技術(shù),在Java里應(yīng)用廣泛,尤其是在hibernate和spring這兩種框架里,在AOP,權(quán)限控制,事務(wù)管理等方面都有動態(tài)代理的實現(xiàn)。JDK本身有實現(xiàn)動態(tài)代理技術(shù),但是略有限制,即被代理的類必須實現(xiàn)某個接口,否則無法使用JDK自帶的動態(tài)代理,因此,如果不滿足條件,就只能使用另一種更加靈活,功能更加強(qiáng)大的動態(tài)代理技術(shù)—— CGLIB。Spring里會自動在JDK的代理和CGLIB之間切換,同時我們也可以強(qiáng)制Spring使用CGLIB。下面我們就動態(tài)代理方面的知識點(diǎn)從頭至尾依次介紹一下。 我們先來看一個例子: 新建一個接口,UserService.java, 只有一個方法add()。 - package com.adam.java.basic;
-
- public interface UserService {
- public abstract void add();
- }
建一個該接口的實現(xiàn)類UserServiceImpl.java
- package com.adam.java.basic;
- public class UserServiceImpl implements UserService {
-
- @Override
- public void add() {
- System.out.println("----- add -----");
- }
- }
建一個代理處理類MyInvocationHandler.java
- package com.adam.java.basic;
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
- import java.lang.reflect.Proxy;
-
- public class MyInvocationHandler implements InvocationHandler {
-
- private Object target;
-
- public MyInvocationHandler(Object target) {
- super();
- this.target = target;
- }
-
- public Object getProxy() {
- return Proxy.newProxyInstance(Thread.currentThread()
- .getContextClassLoader(), target.getClass().getInterfaces(),
- this);
- }
-
- @Override
- public Object invoke(Object proxy, Method method, Object[] args)
- throws Throwable {
- System.out.println("----- before -----");
- Object result = method.invoke(target, args);
- System.out.println("----- after -----");
- return result;
- }
- }
測試類
- package com.adam.java.basic;
- public class DynamicProxyTest {
-
- public static void main(String[] args) {
- UserService userService = new UserServiceImpl();
- MyInvocationHandler invocationHandler = new MyInvocationHandler(
- userService);
-
- UserService proxy = (UserService) invocationHandler.getProxy();
- proxy.add();
- }
- }
執(zhí)行測試類,得到如下輸出: ----- before ----- ----- add ----- ----- after ----- 到這里,我們應(yīng)該會想到點(diǎn)問題: 1. 這個代理對象是由誰且怎么生成的? 2. invoke方法是怎么調(diào)用的? 3. invoke和add方法有什么對應(yīng)關(guān)系? 4. 生成的代理對象是什么樣子的? 帶著這些問題,我們看一下源碼。首先,我們的入口便是上面測試類里的getProxy()方法,我們跟進(jìn)去,看看這個方法:
- public Object getProxy() {
- return Proxy.newProxyInstance(Thread.currentThread()
- .getContextClassLoader(), target.getClass().getInterfaces(),this);
- }
也就是說,JDK的動態(tài)代理,是通過一個叫Proxy的類來實現(xiàn)的,我們繼續(xù)跟進(jìn)去,看看Proxy類的newProxyInstance()方法。先來看看JDK的注釋:
- /**
- * Returns an instance of a proxy class for the specified interfaces
- * that dispatches method invocations to the specified invocation
- * handler.
- *
- * <p>{@code Proxy.newProxyInstance} throws
- * {@code IllegalArgumentException} for the same reasons that
- * {@code Proxy.getProxyClass} does.
- *
- * @param loader the class loader to define the proxy class
- * @param interfaces the list of interfaces for the proxy class
- * to implement
- * @param h the invocation handler to dispatch method invocations to
- * @return a proxy instance with the specified invocation handler of a
- * proxy class that is defined by the specified class loader
- * and that implements the specified interfaces
根據(jù)JDK注釋我們得知,newProxyInstance方法最終將返回一個實現(xiàn)了指定接口的類的實例,其三個參數(shù)分別是:ClassLoader,指定的接口及我們自己定義的InvocationHandler類。我摘幾條關(guān)鍵的代碼出來,看看這個代理類的實例對象到底是怎么生成的。
- Class<?> cl = getProxyClass0(loader, intfs);
- ...
- final Constructor<?> cons = cl.getConstructor(constructorParams);
- ...
- return cons.newInstance(new Object[]{h});
有興趣的同學(xué)可以自己看看JDK的源碼,當(dāng)前我用的版本是JDK1.8.25,每個版本實現(xiàn)方式可能會不一樣,但基本一致,請研究源碼的同學(xué)注意這一點(diǎn)。上面的代碼表明,首先通過getProxyClass獲得這個代理類,然后通過c1.getConstructor()拿到構(gòu)造函數(shù),最后一步,通過cons.newInstance返回這個新的代理類的一個實例,注意:調(diào)用newInstance的時候,傳入的參數(shù)為h,即我們自己定義好的InvocationHandler類,先記著這一步,后面我們就知道這里這樣做的原因。其實這三條代碼,核心就是這個getProxyClass方法,另外兩行代碼是Java反射的應(yīng)用,和我們當(dāng)前的興趣點(diǎn)沒什么關(guān)系,所以我們繼續(xù)研究這個getProxyClass方法。這個方法,注釋很簡單,如下: - /*
- * Look up or generate the designated proxy class.
- */
- Class<?> cl = getProxyClass0(loader, intfs);
就是生成這個關(guān)鍵的代理類,我們跟進(jìn)去看一下。
- private static Class<?> getProxyClass0(ClassLoader loader,
- Class<?>... interfaces) {
- 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);
- }
這里用到了緩存,先從緩存里查一下,如果存在,直接返回,不存在就新創(chuàng)建。在這個get方法里,我們看到了如下代碼: Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter)); 此處提到了apply(),是Proxy類的內(nèi)部類ProxyClassFactory實現(xiàn)其接口的一個方法,具體實現(xiàn)如下:
- public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
-
- Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
- for (Class<?> intf : interfaces) {
- /*
- * Verify that the class loader resolves the name of this
- * interface to the same Class object.
- */
- Class<?> interfaceClass = null;
- try {
- interfaceClass = Class.forName(intf.getName(), false, loader);
- } catch (ClassNotFoundException e) {
- }
- if (interfaceClass != intf) {
- throw new IllegalArgumentException(
- intf + " is not visible from class loader");
- }...
看到Class.forName()的時候,我想大多數(shù)人會笑了,終于看到熟悉的方法了,沒錯!這個地方就是要加載指定的接口,既然是生成類,那就要有對應(yīng)的class字節(jié)碼,我們繼續(xù)往下看:
- /*
- * Generate the specified proxy class.
- */
- byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
- proxyName, interfaces, accessFlags);
- try {
- return defineClass0(loader, proxyName,
- proxyClassFile, 0, proxyClassFile.length);
這段代碼就是利用ProxyGenerator為我們生成了最終代理類的字節(jié)碼文件,即getProxyClass0()方法的最終返回值。所以讓我們回顧一下最初的四個問題:1. 這個代理對象是由誰且怎么生成的? 2. invoke方法是怎么調(diào)用的? 3. invoke和add方法有什么對應(yīng)關(guān)系? 4. 生成的代理對象是什么樣子的? 對于第一個問題,我想答案已經(jīng)清楚了,我再屢一下思路:由Proxy類的getProxyClass0()方法生成目標(biāo)代理類,然后拿到該類的構(gòu)造方法,最后通過反射的newInstance方法,產(chǎn)生代理類的實例對象。接下來,我們看看其他的三個方法,我想先從第四個入手,因為有了上面的生成字節(jié)碼的代碼,那我們可以模仿這一步,自己生成字節(jié)碼文件看看,所以,我用如下代碼,生成了這個最終的代理類。 - package com.adam.java.basic;
-
- import java.io.FileOutputStream;
- import java.io.IOException;
- import sun.misc.ProxyGenerator;
-
- public class DynamicProxyTest {
-
- public static void main(String[] args) {
- UserService userService = new UserServiceImpl();
- MyInvocationHandler invocationHandler = new MyInvocationHandler(
- userService);
-
- UserService proxy = (UserService) invocationHandler.getProxy();
- proxy.add();
-
- String path = "C:/$Proxy0.class";
- byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0",
- UserServiceImpl.class.getInterfaces());
- FileOutputStream out = null;
-
- try {
- out = new FileOutputStream(path);
- out.write(classFile);
- out.flush();
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- try {
- out.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }
上面測試方法里的proxy.add(),此處的add()方法,就已經(jīng)不是原始的UserService里的add()方法了,而是新生成的代理類的add()方法,我們將生成的$Proxy0.class文件用jd-gui打開,我去掉了一些代碼,add()方法如下:
- public final void add()
- throws
- {
- try
- {
- this.h.invoke(this, m3, null);
- return;
- }
- catch (Error|RuntimeException localError)
- {
- throw localError;
- }
- catch (Throwable localThrowable)
- {
- throw new UndeclaredThrowableException(localThrowable);
- }
- }
核心就在于this.h.invoke(this. m3, null);此處的h是啥呢?我們看看這個類的類名:public final class $Proxy0 extends Proxy implements UserService 不難發(fā)現(xiàn),新生成的這個類,繼承了Proxy類實現(xiàn)了UserService這個方法,而這個UserService就是我們指定的接口,所以,這里我們基本可以斷定,JDK的動態(tài)代理,生成的新代理類就是繼承了Proxy基類,實現(xiàn)了傳入的接口的類。那這個h到底是啥呢?我們再看看這個新代理類,看看構(gòu)造函數(shù): - public $Proxy0(InvocationHandler paramInvocationHandler)
- throws
- {
- super(paramInvocationHandler);
- }
構(gòu)造函數(shù)里傳入了一個InvocationHandler類型的參數(shù),看到這里,我們就應(yīng)該想到之前的一行代碼:return cons.newInstance(new Object[]{h}); 這是newInstance方法的最后一句,傳入的h,就是這里用到的h,也就是我們最初自己定義的MyInvocationHandler類的實例。所以,我們發(fā)現(xiàn),其實最后調(diào)用的add()方法,其實調(diào)用的是MyInvocationHandler的invoke()方法。我們再來看一下這個方法,找一下m3的含義,繼續(xù)看代理類的源碼: - static
- {
- try
- {
- m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
- m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
- m3 = Class.forName("com.adam.java.basic.UserService").getMethod("add", new Class[0]);
- m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
- return;
- }
驚喜的發(fā)現(xiàn),原來這個m3,就是原接口的add()方法,看到這里,還有什么不明白的呢?我想2,3,4問題都應(yīng)該迎刃而解了吧?我們繼續(xù),看看原始MyInvocationHandler里的invoke()方法:
- <span style="white-space:pre"> </span>@Override
- public Object invoke(Object proxy, Method method, Object[] args)
- throws Throwable {
- System.out.println("----- before -----");
- Object result = method.invoke(target, args);
- System.out.println("----- after -----");
- return result;
- }
m3就是將要傳入的method,所以,為什么先輸出before,后輸出after,到這里是不是全明白了呢?這,就是JDK的動態(tài)代理整個過程,不難吧?最后,我稍微總結(jié)一下JDK動態(tài)代理的操作過程: 1. 定義一個接口,該接口里有需要實現(xiàn)的方法,并且編寫實際的實現(xiàn)類。 2. 定義一個InvocationHandler類,實現(xiàn)InvocationHandler接口,重寫invoke()方法,且添加getProxy()方法。 總結(jié)一下動態(tài)代理實現(xiàn)過程: 1. 通過getProxyClass0()生成代理類。 2. 通過Proxy.newProxyInstance()生成代理類的實例對象,創(chuàng)建對象時傳入InvocationHandler類型的實例。 3. 調(diào)用新實例的方法,即此例中的add(),即原InvocationHandler類中的invoke()方法。 好了,寫了這么多,也該結(jié)尾了,感謝博友Rejoy的一篇文章,讓我有了參考。同時歡迎大家一起提問討論,如有問題,請留言,我會抽空回復(fù)。相關(guān)代碼已經(jīng)上傳至百度網(wǎng)盤,下載地址。 聯(lián)系方式: 郵箱:xtfggef@gmail.com 微博:http://weibo.com/xtfggef
|