Java 動態代理深入解析
要想了解Java動態代理,首先要了解什麼叫做代理,熟悉設計模式的朋友一定知道在Gof總結的23種設計模式中,有一種叫做代理(Proxy)的物件結構型模式,動態代理中的代理,指的就是這種設計模式。
在我看來所謂的代理模式,和23種設計模式中的“裝飾模式”是一個東西。23種設計模式中將它們作為兩種模式,網上也有些文章講這兩種模式的異同,從細節來看,確實可以人為地區分這兩種模式,但是抽象到一定高度後,我認為這兩種模式是完全一樣的。因此學會了代理模式,也就同時掌握了裝飾模式。
代理模式
代理模式簡單來說,就是對一個物件進行包裝,包裝後生成的物件具有和原物件一樣的方法列表,但是每個方法都可以是被包裝過的。
靜態代理
讓我們先來看一段程式碼:
package common; public class Test { static interface Subject{ void sayHi(); void sayHello(); } static class SubjectImpl implements Subject{ @Override public void sayHi() { System.out.println("hi"); } @Override public void sayHello() { System.out.println("hello"); } } static class SubjectImplProxy implements Subject{ private Subject target; public SubjectImplProxy(Subject target) { this.target=target; } @Override public void sayHi() { System.out.print("say:"); target.sayHi(); } @Override public void sayHello() { System.out.print("say:"); target.sayHello(); } } public static void main(String[] args) { Subject subject=new SubjectImpl(); Subject subjectProxy=new SubjectImplProxy(subject); subjectProxy.sayHi(); subjectProxy.sayHello(); } }
這段程式碼中首先定義了一個Subject介面,介面中有兩個方法。
然後定義了SubjectImpl類實現Subject介面並實現其中的兩個方法,到這裡肯定是沒問題的。
現在再定義一個SubjuectImplProxy類,也實現Subject介面。這個SubjectImplProxy類的作用是包裝SubjectImpl類的例項,它的內部定義一個變數target來儲存一個SubjectImpl的例項。SubjectImplProxy也實現了介面規定的兩個方法,並且在它的實現版本中,都呼叫了SubjectImpl的實現,但是又新增了自己的處理邏輯。
相信這段程式碼不難理解,它通過對SubjectImpl進行包裝,達到了給輸出內容新增字首的功能。這種代理方式叫做靜態代理。
動態代理
從上面的演示中我們不難看出靜態代理的缺點:我們對SubjectImpl的兩個方法,是進行的相同的包裝,但是卻要在SubjectImplProxy裡把相同的包裝邏輯寫兩次,而且以後如果Subject介面再新增新的方法,SubjectImplProxy也必須要新增新的實現,儘管SubjectImplProxy對所有方法的包裝可能都是一樣的。
下面我把上面例子的靜態代理改成動態代理,我們來看一下區別:
package common; import java.lang.invoke.MethodHandle; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class Test { static interface Subject{ void sayHi(); void sayHello(); } static class SubjectImpl implements Subject{ @Override public void sayHi() { System.out.println("hi"); } @Override public void sayHello() { System.out.println("hello"); } } static class ProxyInvocationHandler implements InvocationHandler{ private Subject target; public ProxyInvocationHandler(Subject target) { this.target=target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.print("say:"); return method.invoke(target, args); } } public static void main(String[] args) { Subject subject=new SubjectImpl(); Subject subjectProxy=(Subject) Proxy.newProxyInstance(subject.getClass().getClassLoader(), subject.getClass().getInterfaces(), new ProxyInvocationHandler(subject)); subjectProxy.sayHi(); subjectProxy.sayHello(); } }
只看main方法的話,只有第二行和之前的靜態代理不同,同樣是生成一個subjectProxy代理物件,只是生成的程式碼不同了。靜態代理是直接new 一個SubjectImplProxy的例項,而動態代理則呼叫了java.lang.reflect.Proxy.newProxyInstance()方法,我們來看一下這個方法的原始碼:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { if (h == null) { throw new NullPointerException(); } /* * Look up or generate the designated proxy class. */ Class<?> cl = getProxyClass(loader, interfaces); //獲取代理類的Class /* * Invoke its constructor with the designated invocation handler. */ try { Constructor cons = cl.getConstructor(constructorParams); //constructorParams是寫死的:{ InvocationHandler.class },上邊返回的代理類Class一定是extends Proxy的,而Proxy有一個引數為InvocationHandler的建構函式 return cons.newInstance(new Object[] { h }); //這裡通過建構函式將我們自己定義的InvocationHandler的子類傳到代理類的例項裡,當我們呼叫代理類的任何方法時,實際上都會呼叫我們定義的InvocationHandler子類重寫的invoke()函式 } catch (NoSuchMethodException e) { throw new InternalError(e.toString()); } catch (IllegalAccessException e) { throw new InternalError(e.toString()); } catch (InstantiationException e) { throw new InternalError(e.toString()); } catch (InvocationTargetException e) { throw new InternalError(e.toString()); } }
上面的 Class<?> cl = getProxyClass(loader, interfaces); 呼叫的getProxyClass方法:
public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) throws IllegalArgumentException { if (interfaces.length > 65535) { //因為在class檔案中,一個類儲存的介面數量是用2個位元組來表示的,因此java中一個類最多可以實現65535個介面 throw new IllegalArgumentException("interface limit exceeded"); } Class<?> proxyClass = null; /* collect interface names to use as key for proxy class cache */ String[] interfaceNames = new String[interfaces.length]; // for detecting duplicates Set<Class<?>> interfaceSet = new HashSet<>(); //驗證interfaces裡的介面是否能被類載入器載入,是否是介面,是否有重複的 for (int i = 0; i < interfaces.length; i++) { /* * Verify that the class loader resolves the name of this * interface to the same Class object. */ String interfaceName = interfaces[i].getName(); Class<?> interfaceClass = null; try { interfaceClass = Class.forName(interfaceName, false, loader); } catch (ClassNotFoundException e) { } if (interfaceClass != interfaces[i]) { throw new IllegalArgumentException( interfaces[i] + " is not visible from class loader"); } /* * Verify that the Class object actually represents an * interface. */ if (!interfaceClass.isInterface()) { throw new IllegalArgumentException( interfaceClass.getName() + " is not an interface"); } /* * Verify that this interface is not a duplicate. */ if (interfaceSet.contains(interfaceClass)) { throw new IllegalArgumentException( "repeated interface: " + interfaceClass.getName()); } interfaceSet.add(interfaceClass); interfaceNames[i] = interfaceName; } /* * Using string representations of the proxy interfaces as * keys in the proxy class cache (instead of their Class * objects) is sufficient because we require the proxy * interfaces to be resolvable by name through the supplied * class loader, and it has the advantage that using a string * representation of a class makes for an implicit weak * reference to the class. */ List<String> key = Arrays.asList(interfaceNames); //使用interfaces列表作為key快取在cache裡,也就是實現了相同interfaces的代理類只會建立載入一次 /* * Find or create the proxy class cache for the class loader. */ Map<List<String>, Object> cache; synchronized (loaderToCache) { cache = loaderToCache.get(loader); if (cache == null) { cache = new HashMap<>(); loaderToCache.put(loader, cache); } /* * This mapping will remain valid for the duration of this * method, without further synchronization, because the mapping * will only be removed if the class loader becomes unreachable. */ } /* * Look up the list of interfaces in the proxy class cache using * the key. This lookup will result in one of three possible * kinds of values: * null, if there is currently no proxy class for the list of * interfaces in the class loader, * the pendingGenerationMarker object, if a proxy class for the * list of interfaces is currently being generated, * or a weak reference to a Class object, if a proxy class for * the list of interfaces has already been generated. */ //看看快取裡有沒有,如果有就直接取出來然後return,否則判斷根據pendingGenerationMarker判斷是否有其它執行緒正在生成當前的代理類,如果有則cache.wait()等待,如果沒有則建立。 synchronized (cache) { /* * Note that we need not worry about reaping the cache for * entries with cleared weak references because if a proxy class * has been garbage collected, its class loader will have been * garbage collected as well, so the entire cache will be reaped * from the loaderToCache map. */ do { Object value = cache.get(key); if (value instanceof Reference) { proxyClass = (Class<?>) ((Reference) value).get(); } if (proxyClass != null) { // proxy class already generated: return it return proxyClass; } else if (value == pendingGenerationMarker) { // proxy class being generated: wait for it try { cache.wait(); } catch (InterruptedException e) { /* * The class generation that we are waiting for should * take a small, bounded time, so we can safely ignore * thread interrupts here. */ } continue; } else { /* * No proxy class for this list of interfaces has been * generated or is being generated, so we will go and * generate it now. Mark it as pending generation. */ cache.put(key, pendingGenerationMarker); break; } } while (true); } //確認要生成的代理類所屬的包,如果interfaces裡所有介面都是public的,代理類所屬包就是預設包;如果有interface不是public,那麼所有不是public的interface必須在一個包裡否則報錯。 try { String proxyPkg = null; // package to define proxy class in /* * 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 (int i = 0; i < interfaces.length; i++) { int flags = interfaces[i].getModifiers(); if (!Modifier.isPublic(flags)) { String name = interfaces[i].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"); } } } if (proxyPkg == null) { // if no non-public proxy interfaces, proxyPkg = ""; // use the unnamed package } { /* * Choose a name for the proxy class to generate. */ long num; synchronized (nextUniqueNumberLock) { num = nextUniqueNumber++; } String proxyName = proxyPkg + proxyClassNamePrefix + num; //生成代理類的名字,proxyPkg是上面確定下來的代理類所在的包名,proxyClassNamePrefix是寫死的字串“$Proxy”,num是一個全域性唯一的long型數字,從0開始累積,每次生成新的代理類就+1,從這裡也能看出生成的動態代理類的數量不能超過Long.maxValue /* * Verify that the class loader hasn't already * defined a class with the chosen name. */ /* * Generate the specified proxy class. */ byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces); //生成一個以proxyName為類名的,實現了Interfaces裡所有介面的類的位元組碼 try { proxyClass = defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); //載入生成的類 } catch (ClassFormatError e) { /* * A ClassFormatError here means that (barring bugs in the * proxy class generation code) there was some other * invalid aspect of the arguments supplied to the proxy * class creation (such as virtual machine limitations * exceeded). */ throw new IllegalArgumentException(e.toString()); } } // add to set of all generated proxy classes, for isProxyClass proxyClasses.put(proxyClass, null); } finally { /* * We must clean up the "pending generation" state of the proxy * class cache entry somehow. If a proxy class was successfully * generated, store it in the cache (with a weak reference); * otherwise, remove the reserved entry. In all cases, notify * all waiters on reserved entries in this cache. */ //建立成功,則將cache中該key的pendingGenerationMarker替換為實際的代理類的弱引用,否則也要清除pendingGenerationMarker標記;不管是否成功,都要執行cache.notifyAll(),讓其它要建立相同代理類並且執行了cache.wait()的執行緒恢復執行。
synchronized (cache) { if (proxyClass != null) { cache.put(key, new WeakReference<Class<?>>(proxyClass)); } else { cache.remove(key); } cache.notifyAll(); } } return proxyClass; //最後返回代理類Class }
到這裡,我們已經把動態代理的java原始碼都解析完了,現在思路就很清晰了:
Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法簡單來說執行了以下操作:
1.生成一個實現了引數interfaces裡所有介面且繼承了Proxy的代理類的位元組碼,然後用引數裡的classLoader載入這個代理類。
2.使用代理類父類的建構函式 Proxy(InvocationHandler h)來創造一個代理類的例項,將我們自定義的InvocationHandler的子類傳入。
3.返回這個代理類例項,因為我們構造的代理類實現了interfaces(也就是我們程式中傳入的subject.getClass().getInterfaces())裡的所有介面,因此返回的代理類可以強轉成Subject型別來呼叫介面中定義的方法。
現在我們知道了用Proxy.newProxyInstance()返回的subjectProxy可以成功強轉成Subject型別來呼叫介面中定義的方法了,那麼在呼叫方法後,代理類例項怎麼進行處理的呢,這就需要看一下代理類的原始碼了。但是代理類是程式動態生成位元組碼載入的,怎麼看原始碼呢?沒關係,可以在main方法中加入System.getProperties().put(“sun.misc.ProxyGenerator.saveGeneratedFiles”,”true”),這樣就會把生成的代理類Class檔案儲存在本地磁碟上,然後再反編譯可以得到代理類的原始碼:
package common; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; public final class $Proxy0 extends Proxy implements Test.Subject { private static Method m4; private static Method m1; private static Method m3; private static Method m0; private static Method m2; static { try { m4 = Class.forName("Test$Subject").getMethod("sayHello", new Class[0]); m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); m3 = Class.forName("Test$Subject").getMethod("sayHi", new Class[0]); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); } catch (Exception e) { throw new RuntimeException(e); } } public $Proxy0(InvocationHandler paramInvocationHandler) { super(paramInvocationHandler); } public final void sayHello() { try { this.h.invoke(this, m4, null); return; } catch (RuntimeException localRuntimeException) { throw localRuntimeException; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final boolean equals(Object paramObject) { try { return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue(); } catch (RuntimeException localRuntimeException) { throw localRuntimeException; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final void sayHi() { try { this.h.invoke(this, m3, null); return; } catch (RuntimeException localRuntimeException) { throw localRuntimeException; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final int hashCode() { try { return ((Integer)this.h.invoke(this, m0, null)).intValue(); } catch (RuntimeException localRuntimeException) { throw localRuntimeException; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final String toString() { try { return (String)this.h.invoke(this, m2, null); } catch (RuntimeException localRuntimeException) { throw localRuntimeException; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } }
我們可以看到代理類內部實現比較簡單,在呼叫每個代理類每個方法的時候,都用反射去調h的invoke方法(也就是我們自定義的InvocationHandler的子類中重寫的invoke方法),用引數傳遞了代理類例項、介面方法、呼叫引數列表,這樣我們在重寫的invoke方法中就可以實現對所有方法的統一包裝了。
總結
動態代理相對於靜態代理在使用上的優點主要是能夠對一個物件的所有方法進行統一包裝,而且後期被代理的類新增方法的時候動態代理類不需要改動。
缺點是要求被代理的類必須實現了介面,因為動態代理類在實現的時候繼承了Proxy類,java不支援多繼承,因此動態代理類只能根據介面來定義方法。
最後動態代理之所以叫做動態代理是因為java在實現動態代理的時候,動態代理類是在執行時動態生成和載入的,相對的,靜態代理類和其他普通類一下,在類載入階段就載入了。
相關文章
- 深入理解 Java 反射和動態代理Java反射
- Java 靜態代理和動態代理的使用及原理解析Java
- java基礎:深入理解JDK動態代理JavaJDK
- JAVA 靜態代理 & 動態代理Java
- 深入理解動態代理
- Java代理(jdk靜態代理、動態代理和cglib動態代理)JavaJDKCGLib
- 深入理解靜態代理與JDK動態代理JDK
- java靜態代理和動態代理Java
- java動態代理Java
- 【JAVA】代理模式之Java動態代理Java模式
- java動態代理動態在哪裡?Java
- Java動態代理(AOP)Java
- java動態代理(1)Java
- java 的動態代理Java
- Java Proxy動態代理Java
- java動態代理原理Java
- 輕鬆理解 Java 靜態代理/動態代理Java
- Java中的靜態代理和動態代理Java
- Retrofit原始碼解析-動態代理原始碼
- Retrofit 原始碼解析 - 動態代理原始碼
- Java動態程式設計---動態代理Java程式設計
- Java進階--Java動態代理Java
- Java基礎系列-靜態代理和動態代理Java
- JAVA學習篇--靜態代理VS動態代理Java
- Java 動態代理newProxyInstanceJava
- 動態代理及java演示Java
- java動態代理如何使用Java
- java 反射和動態代理Java反射
- Java Web之動態代理JavaWeb
- (轉)Java動態代理與CGLib代理JavaCGLib
- Java進階之 JDK動態代理與Cglib動態代理JavaJDKCGLib
- 深入理解JDK動態代理機制JDK
- 深入淺出MyBatis:反射和動態代理MyBatis反射
- Java架構-Java JDK 動態代理Java架構JDK
- 讓我們打一場動態代理的官司–Java動態代理Java
- java | 什麼是動態代理?Java
- java 動態代理簡單使用Java
- 對JAVA動態代理的理解Java