在 Java 中,動態代理是一個很常用的功能,雖然說一般不需要自己直接去用,但是瞭解它們是怎麼回事還是很有必要的。
這篇博客的主要內容便是 JDK 動態代理和 CGLIB 動態代理的簡單使用和理解。
JDK 動態代理
JDK 動態代理依賴於 接口 來確定它需要代理的方法,使用時可以分為以下幾個角色:
- TargetInterfaces - 需要代理的目標接口(們),JDK 動態代理將會為這些接口的 方法調用 創建代理
- TargetObject - 實現了目標接口的對象
- InvocationHandler - 方法調用 處理器,JDK 動態代理在內部通過 InvocationHandler 對象來處理目標方法的調用
- java.lang.reflect.Proxy - 組裝 InvocationHandler 和 TargetObject, 創建代理對象,創建出來的代理對象是它的子類的實例
TargetInterfaces 和 TargetObject 相對來說很容易理解,就是一些接口和實現了這些接口的對象,比如說:
<code>interface TargetInterfaceA { void targetMethodA();}interface TargetInterfaceB { void targetMethodB();}class TargetClass implements TargetInterfaceA, TargetInterfaceB { @Override public void targetMethodA() { System.out.println("Target method A..."); } @Override public void targetMethodB() { System.out.println("Target method B..."); }}/<code>
上面的例子中,目標接口為 [TargetInterfaceA, TargetInterfaceB], 而目標對象就是 TargetClass 的 實例.
現在,我們想要攔截 TargetClass 實現的接口的方法的調用,我們需要先通過 InvocationHandler 來定義代理邏輯:
<code>class SimpleInvocationHandler implements InvocationHandler { private Object target; public SimpleInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(String.format("Before invocation method %s", method.getName())); Object result = method.invoke(target, args); System.out.println(String.format("After invocation method %s", method.getName())); return result; } }/<code>
InvocationHandler 這個接口只定義了一個方法 invoke, 該方法的參數為:
- proxy - 代理對象實例,注意,不是 TargetObject, 是 Proxy 子類的實例,因此,我們需要在 InvocationHandler 實例內部持有 TargetObject
- method - 要調用的方法
- args - 方法調用參數
有了 InvocationHandler 和 TargetClass 之後,我們就可以創建 TargetObject 並通過 Proxy 組裝創建代理對象了,主要通過 newProxyInstance 方法完成:
<code>TargetClass targetObject = new TargetClass();Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), new SimpleInvocationHandler(targetObject););/<code>
方法 Proxy.newProxyInstance 的參數為:
- ClassLoader - 一個 ClassLoader, 簡單的話直接用 targetObject 的 ClassLoader 就可以了
- Class>[] - 要代理的接口數組,同樣,直接獲取 targetObject 實現的所有接口
- InvocationHandler - 定義了方法調用處理邏輯的 InvocationHandler
PS: 可以看到,創建代理對象的時候需要我們先創建 TargetObject 才行,而且還需要手動將 TargetObject 傳遞給 InvocationHandler, 很麻煩……
完整代碼和測試:
<code>interface TargetInterfaceA { void targetMethodA();}interface TargetInterfaceB { void targetMethodB();}class TargetClass implements TargetInterfaceA, TargetInterfaceB { @Override public void targetMethodA() { System.out.println("Target method A..."); } @Override public void targetMethodB() { System.out.println("Target method B..."); }}class SimpleInvocationHandler implements InvocationHandler { private Object target; public SimpleInvocationHandler(Object target) { this.target = target; } public static Object bind(Object targetObject) { SimpleInvocationHandler handler = new SimpleInvocationHandler(targetObject); return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), handler); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(String.format("Before invocation method %s", method.getName())); Object result = method.invoke(target, args); System.out.println(String.format("After invocation method %s", method.getName())); return result; }}public class ProxyTest { public static void main(String[] args) { Object proxy = SimpleInvocationHandler.bind(new TargetClass()); ((TargetInterfaceA) proxy).targetMethodA(); ((TargetInterfaceB) proxy).targetMethodB(); }}/<code>
輸出為:
<code>Before invocation method targetMethodATarget method A...After invocation method targetMethodABefore invocation method targetMethodBTarget method B...After invocation method targetMethodB/<code>
代理類
在運行代碼時可以將下面這行代碼放在最前面查看 Proxy 動態生成的代理類是怎樣的:
<code>System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");/<code>
前面的代碼生成的代理類為:
<code>final class $Proxy0 extends Proxy implements TargetInterfaceA, TargetInterfaceB { private static Method m0; private static Method m1; private static Method m2; private static Method m4; private static Method m3; static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m2 = Class.forName("java.lang.Object").getMethod("toString"); // 目標接口中定義的方法 m4 = Class.forName("classload.TargetInterfaceB").getMethod("targetMethodB"); m3 = Class.forName("classload.TargetInterfaceA").getMethod("targetMethodA"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } public $Proxy0(InvocationHandler var1) throws { super(var1); } // 通過 InvocationHandler 來調用目標方法 public final void targetMethodA() throws { try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } // 通過 InvocationHandler 來調用目標方法 public final void targetMethodB() throws { try { super.h.invoke(this, m4, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } }}/<code>
通過閱讀代理類的代碼我們可以發現:
- 代理類繼承了 Proxy 並實現了目標接口
- 代理類在靜態初始化塊通過反射獲取了目標接口的方法
- 代理類實現的接口方法會通過 InvocationHandler 來調用目標方法
- InvocationHandler 傳遞的第一個參數為代理對象,不是 TargetObject1
另外,代理類還獲取了 Object 的 hashCode、equals 和 toString 方法,它們的調用邏輯都是一樣的,就是直接調用 InvocationHandler 對象對應的方法,比如:
<code>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); }}/<code>
因此,我們也是可以代理目標對象的這些方法的。
CGLIB 動態代理
CGLIB 動態代理和 JDK 動態代理類似,只不過 CGLIB 動態代理是基於類的,不需要實現接口,簡單使用的話只需要定義一個 MethodInterceptor 就可以了, 相當於 JDK 動態代理中的 InvocationHandler.
<code>class SimpleMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println(String.format("Before invocation method %s", method.getName())); Object result = proxy.invokeSuper(obj, args); System.out.println(String.format("After invocation method %s", method.getName())); return result; }}/<code>
有了 MethodInterceptor 後我們就可以創建代理對象了:
<code>class ProxyTest { public static void main(String[] args) { Enhancer enhancer = new Enhancer(); // 設置需要被代理的類 enhancer.setSuperclass(TargetClass.class); // 設置 MethodInterceptor enhancer.setCallback(new SimpleMethodInterceptor()); // 創建代理對象 TargetClass proxyObject = (TargetClass) enhancer.create(); // 調用方法 proxyObject.targetMethodA(); proxyObject.targetMethodB(); }}class TargetClass { public void targetMethodA() { System.out.println("Target method A..."); } public void targetMethodB() { System.out.println("Target method B..."); }}/<code>
執行輸出為:
<code>Before invocation method targetMethodATarget method A...After invocation method targetMethodABefore invocation method targetMethodBTarget method B...After invocation method targetMethodB/<code>
代理類
對於 CGLIB 來說可以設置 DebuggingClassWriter.DEBUG_LOCATION_PROPERTY 屬性的值來保存生成的代理類2:
<code>public class TargetClass$$EnhancerByCGLIB$$eb42b691 extends TargetClass implements Factory { private MethodInterceptor CGLIB$CALLBACK_0; static void CGLIB$STATICHOOK1() { // 要代理的目標方法 var10000 = ReflectUtils.findMethods(new String[]{"targetMethodA", "()V", "targetMethodB", "()V"}, (var1 = Class.forName("TargetClass")).getDeclaredMethods()); CGLIB$targetMethodA$0$Method = var10000[0]; CGLIB$targetMethodA$0$Proxy = MethodProxy.create(var1, var0, "()V", "targetMethodA", "CGLIB$targetMethodA$0"); CGLIB$targetMethodB$1$Method = var10000[1]; CGLIB$targetMethodB$1$Proxy = MethodProxy.create(var1, var0, "()V", "targetMethodB", "CGLIB$targetMethodB$1"); } // 目標方法的簡單代理 final void CGLIB$targetMethodA$0() { super.targetMethodA(); } public final void targetMethodA() { MethodInterceptor var10000 = this.CGLIB$CALLBACK_0; if (var10000 == null) { CGLIB$BIND_CALLBACKS(this); var10000 = this.CGLIB$CALLBACK_0; } // 當 MethodInterceptor 不為空時通過 MethodInterceptor 調用目標方法 if (var10000 != null) { var10000.intercept(this, CGLIB$targetMethodA$0$Method, CGLIB$emptyArgs, CGLIB$targetMethodA$0$Proxy); } else { super.targetMethodA(); } } // 目標方法的簡單代理 final void CGLIB$targetMethodB$1() { super.targetMethodB(); } public final void targetMethodB() { MethodInterceptor var10000 = this.CGLIB$CALLBACK_0; if (var10000 == null) { CGLIB$BIND_CALLBACKS(this); var10000 = this.CGLIB$CALLBACK_0; } // 當 MethodInterceptor 不為空時通過 MethodInterceptor 調用目標方法 if (var10000 != null) { var10000.intercept(this, CGLIB$targetMethodB$1$Method, CGLIB$emptyArgs, CGLIB$targetMethodB$1$Proxy); } else { super.targetMethodB(); } } final int CGLIB$hashCode$5() { return super.hashCode(); } // 對 Object 方法的代理 public final int hashCode() { MethodInterceptor var10000 = this.CGLIB$CALLBACK_0; if (var10000 == null) { CGLIB$BIND_CALLBACKS(this); var10000 = this.CGLIB$CALLBACK_0; } if (var10000 != null) { Object var1 = var10000.intercept(this, CGLIB$hashCode$5$Method, CGLIB$emptyArgs, CGLIB$hashCode$5$Proxy); return var1 == null ? 0 : ((Number)var1).intValue(); } else { return super.hashCode(); } } // ...}/<code>
可以看到
- CGLIB 每個方法設置了兩個代理,一個直接調用父類方法,另一個判斷是否存在 MethodInterceptor 來進行調用
- 代理類繼承了 TargetClass, 和 JDK 動態代理中繼承 Proxy 的方式不一樣
當我們設置了 MethodInterceptor 以後,CGLIB 便可以通過 MethodInterceptor 來調用目標方法,另外,調用 MethodInterceptor.intercept 方法時傳遞的第一個參數為代理類實例, 因此,需要執行被代理的方法時,應該通過 MethodProxy.invokeSuper 來完成,如果使用 Method.invoke 的話就會導致無限遞歸調用。
Spring @Configuration
在使用 Spring 的時候我們可以通過如下方式定義 Bean:
<code>@Configuration@ComponentScan(basePackageClasses = Company.class)public class Config { @Bean public Address getAddress() { return new Address("High Street", 1000); } @Bean public Person getPerson() { return new Person(getAddress()); }}/<code>
當初對於這種方式的一種困惑就是,Spring 是怎麼攔截對 getAddress 方法的調用的,因為在我的印象中 JDK 動態代理做不到這樣的事情,現在才發現,Spring 會通過 CGLIB 為 Config 創建代理對象,攔截對 getAddress 方法的調用,保證 Bean 的單例性。
因為在 Java 中會根據先對象的 實際類型 查找方法,找不到才到 父類 中進行查找,而恰好的是,CGLIB 創建的代理對象是覆蓋了父類的方法的,這樣一來,在代理類中通過 MethodInterceptor 攔截方法的調用就可以避免重複創建 Bean 了。
這在 Spring 中對應的 MethodInterceptor 為 ConfigurationClassEnhancer.BeanMethodInterceptor.
小結
這裡彙總一下 JDK 動態代理和 CGLIB 動態代理的代理方式:
- JDK 動態代理通過創建繼承了 Proxy 並實現了 TargetInterfaces 的代理類來完成代理,調用 TargetInterfaces 的方法時,代理類會將方法調用轉交給 InvocationHandler 完成
- CGLIB 動態代理通過創建繼承了 TargetClass 的代理類來完成代理,調用 TargetClass 的方法時,如果 MethodInterceptor 不為空,那麼就會將方法調用轉交給 MethodInterceptor 完成
可以看到,兩種實現動態代理的方式還是很接近的,只不過一個是通過接口,一個是通過子類。
結語
最開始接觸動態代理這個概念是在看《Java 核心技術卷》這本書的時候,當時剛開始學 Java 沒多久,看到這個東西后的想法就是,這麼不方便的東西, 誰沒事會去用啊 →_→
結果,它的使用很廣泛 ( ´ゝ`)
類似的還有源碼註解,不得不說,這些操作起來麻煩,但是功能又強大的特性,總會有人完成花樣來╮( ̄▽ ̄)╭
Footnotes
1 最開始看到 InvocationHandler 這個接口的時候總以為它的第一個參數為 TargetObject
2 為了方便閱讀省略了很多其他不必要的內容
整理了 1000 道 2019 年多家公司 java 面試題 400 多頁 pdf 文檔,還有幾百頁的Java核心知識點PDF。私信回覆:555領取
閱讀更多 Java耕耘 的文章