1. 代理基本概念:
以下是代理概念的百度解釋:代理(百度百科)
總之一句話:三個元素,數據--->代理對象--->真實對象;復雜一點的可以理解為五個元素:輸入數據--->代理對象--->真實對象--->代理對象--->輸出數據。
2. JDK的動態代理概念:
JDK的動態代理和正常的代理邏輯有些區別。
首先先明確一下術語:類 class ,接口 interface。
JDK動態代理是基於 interface 創建的,而不是真正的對象;也就是說,即使沒有真正的對象,JDK依然可以創建代理對象。下面用代碼來解釋:
public class JDKProxy implements InvocationHandler{ public Object getObject(TestInterface ref){ return Proxy.newProxyInstance(getClass().getClassLoader(), ref.getClass().getInterfaces(), this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return null; } }
當然,實際中使用的情況是有真正的對象的,像下面這樣:
public class JDKProxy implements InvocationHandler{ TestInterface ref; public Object getObject(TestInterface ref){ this.ref = ref; return Proxy.newProxyInstance(getClass().getClassLoader(), ref.getClass().getInterfaces(), this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("doBefore"); Object o = method.invoke(ref, args); System.out.println("doAfter"); return o; } }
那么,和正常的代理邏輯區別就在這里了,JDK的動態代理多依賴一個元素,就是被代理對象ref所實現的接口。如果ref對象沒有實現任何接口,那么這個對象是無法被代理的。
那么問題來了: 為什么Java自帶的動態代理 選擇 要基於接口 ?基於什么考慮,或者說Java如果 選擇 直接 代理真正的對象會有什么問題?
3. 進入正題:JDK動態代理是如何實現的?(基於JDK1.8)
Java中涉及到的關鍵先生:InvocationHandler , Proxy
3.1 使用方法及參數詳細解釋
代碼使用方法:
//代理類,實現InvocationHandler
public class JDKProxy implements InvocationHandler{ private UserService userServiceRef;
//獲取代理對象 public UserService getProxy(UserService userServiceRef){ this.userServiceRef = userServiceRef;
//Proxy生成代理對象 return (UserService) Proxy.newProxyInstance(getClass().getClassLoader(), userServiceRef.getClass().getInterfaces(), this); }
//代理對象做的事 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("someone is logining"); Object returnObject = method.invoke(userServiceRef, args); System.out.println("someone login success"); return returnObject; } }
下面是InvocationHandler的接口描述:
package java.lang.reflect;
public interface InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }
其中的參數:
proxy: 代理對象本身,也就是 getProxy(UserService userServiceRef) 獲取到的對象。大家思考一下,為什么要把這個代理對象作為參數傳進來?
我個人覺得這是個完全沒有必要的參數。
method:userServiceRef中的方法。
args: method方法的參數。
再來看一下Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)方法參數:
loader:類加載器,用來加載代理類的,即Proxy.newProxyInstance()的返回結果的類字節碼。
interfaces:代理對象所實現的接口。 這里接口是數組參數,通常被代理只實現一個接口。那實現多個接口時使用代理對象有什么問題?其實也沒問題,就是調用不同接口的方法前需要先強轉為對應的接口類,麻煩。
h:實現InvocationHandler的類,也就是示例代碼中的JDKProxy類 。
測試代碼:
public static void main(String[] args) { UserService userService = new UserServiceImpl(); JDKProxy proxy = new JDKProxy(); UserService userServiceProxy = proxy.getProxy(userService); userServiceProxy.login(); }
3.2 實現的原理與細節:
1.代理對象的創建過程:
創建代理對象的方法:Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
通過這個方法的參數其實可以看到一些眉目,loader用來加載代理類字節碼,interfaces作為代理類實現的接口,h為代理對象實際調用的方法(即invoke方法)。
創建過程大致分為幾步:
-
- 從緩存中獲取代理對象,獲取到則直接返回;
緩存由WeakCache中的ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map來存儲;
為什么是兩級Map? 想一想,Java類的唯一性由ClassLoader+Class決定,所以Key Object是ClassLoader,Key Object是所有接口組成的對象().
WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
其中KeyFactory的作用就是將interfaces轉換為Key Object 。
-
- 生成代理對象的類名proxyName;
由 private static final class ProxyClassFactory 來完成.
private static final String proxyClassNamePrefix = "$Proxy"; //ProxyClassFactory // 每次使用時 自增1 private static final AtomicLong nextUniqueNumber = new AtomicLong();//ProxyClassFactory
ReflectUtil.PROXY_PACKAGE = "com.sun.proxy";//RelectionUtil
proxyName = PROXY_PACKAGE + proxyClassNamePrefix + nextUniqueNumber;
-
- 生成proxyName類的字節碼;
由 sun.misc.ProxyGenerator.generateProxyClass(String proxyName, Class<?>[] interfaces, int accessFlags) ,方法返回二進制字節碼。
參數: 類名,需要實現的接口,訪問標志。
-
- 將字節碼加載到虛擬機中,即方法區內存(jdk1.7之前是永久代,jdk8之后的元數據區);
由 java.lang.reflect.Proxy.defineClass0(ClassLoader loader, String proxyName, byte[] proxyClass, int offset, int length) 完成。
private static native Class<?> defineClass0(ClassLoader loader, String name, byte[] b, int off, int len);
這是一個本地方法,通過JNI調用,返回代理的Class對象。
-
- 生成代理Class對象的實例;構造器實例化
生成代理對象的三個參數中的 interfaces, classLoader都使用過了,還有一個InvocationHandler h 沒有使用。
通過反射獲取代理Class的參數為InvocationHandler的構造器,通過 Constructor.newInstance(new Object[]{h}); 返回最后的代理實例對象。
創建代理的過程就完成了。
2. 代理對象的字節碼分析:
還是以上面3.1的例子分析,測試代碼稍作修改,如下:
public static void main(String[] args) { System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); //生成的代理對象字節碼保存到.class文件中。 JDKProxy proxy = new JDKProxy(); proxy.getProxy(new UserServiceImpl()); //生成代理對象 }
運行測試代碼之后,user.dir目錄下會多出一個目錄:com/sun/proxy,打開后可以看到$Proxy0.class文件。
jd-gui反編譯該class文件:為方便閱讀,我把方法里的try catch全部移掉了。
package com.sun.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; import myproxy.UserService; public final class $Proxy0 extends Proxy implements UserService { private static Method m1; private static Method m2; private static Method m3; private static Method m0; public $Proxy0(InvocationHandler paramInvocationHandler){ super(paramInvocationHandler); //h引用在父類Proxy中 } public final boolean equals(Object paramObject){ return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue(); } public final String toString(){ return (String)this.h.invoke(this, m2, null); } public final boolean login(){ return ((Boolean)this.h.invoke(this, m3, null)).booleanValue(); //boolean存在包裝和解包裝 } public final int hashCode(){ return ((Integer)this.h.invoke(this, m0, null)).intValue(); } static{ 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("myproxy.UserService").getMethod("login", new Class[0]); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); return; } }
一目了然。主要五部分內容:
代理對象繼承了Proxy類,並實現了目標接口。
生成了以InvocationHandler為參數的構造器,實例化時將我們的自定JDKProxy(實現了InvocationHandler,並持有被代理對象)傳遞進去;
生成了Object中的三個方法:equals, hashCode, toString;
生成了接口中的所有方法,全部調用InvocationHandler對象的invoke方法;
生成了對應方法的Method對象屬性,傳遞給invoke方法。
3 .提示細節:
-
- 代理對象的父類java.lang.reflect.Proxy類是實現了java.io.Serializable接口的,所以代理對象都是可序列化的;
- 對於有參和無參方法,都是通過invoke方法調用,無參方法會直接傳入null,所以在invoke方法中使用args參數時一定要先進行null的判斷;
- 對於原始數據類型(int,boolean等8種),代理對象的方法中參數和返回值都進行了包裝和解包裝。
- 代理對象生成過程中用到了反射,生成字節碼時,反射Object對象方法和反射接口方法;生成實例時,反射獲取代理對象的構造器;代理對象方法調用過程中是沒有使用反射的。
- 有沒有感覺跟 裝飾器模式 有一些 異曲同工 呢?
4. 以上就是個人總結分享的JDK動態代理的內容,原創內容,轉載請注明出處。
個人水平有限,有誤的地方歡迎評論中指正。
byte[]