Java 動態代理的簡單使用和理解


Java 動態代理的簡單使用和理解

在 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.

Java 動態代理的簡單使用和理解

小結

這裡彙總一下 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領取


分享到:


相關文章: