Java進階--Java動態代理

景嶽發表於2020-10-21

JDK version: 1.8

動態代理中所說的“動態”, 是針對使用Java程式碼實際編寫了代理類的“靜態”代理而言的, 它的優勢不在於省去了編寫代理類那一點編碼工作量, 
而是實現了可以在原始類和介面還未知的時候, 就確定代理類的代理行為,當代理類與原始類脫離直接聯絡後, 就可以很靈活地重用於不同的應用場景之中。

  • 目前Java開發包中包含了對動態代理的支援, 但是其實現只支援對介面的的實現。其實現主要通過 java.lang.reflect.Proxy 類和 java.lang.reflect.InvocationHandler 介面。
  • Proxy 類主要用來獲取動態代理物件, InvocationHandler 介面用來約束呼叫者實現。
  • 動態代理是很多框架和技術的基礎, spring 的 AOP 實現就是基於動態代理實現的。

Proxy類
Proxy 提供用於建立動態代理類例項的靜態方法, 它還是與之建立的所有動態代理類的超類。
介紹一下 Proxy 類中最常用的方法 java.lang.reflect.Proxy#newProxyInstance,

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException {
    ...
    return cons.newInstance(new Object[]{h});
    ...
}

該方法返回動態代理類例項. 該方法有三個引數

  • loader: the class loader to define the proxy class. 與原始類的類載入器一致
  • interfaces: the list of interfaces for the proxy class to implement. 原始類實現的介面
  • h: InvocationHandler 例項, 動態代理類例項會呼叫 InvocationHandler 例項的 invoke 方法

InvocationHandler介面
InvocationHandler 介面中只有一個方法.

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;

該方法有三個引數

  • proxy: 動態代理類的例項
  • method: 原始類的 Method 例項
  • args: 被代理 Method 需要的引數

下面通過編寫一個簡單地動態代理類舉例說明:

 1 public class DomainProxyTest {
 2 
 3     interface IGreet {
 4 
 5         void sayHello();
 6 
 7         void sayHi();
 8     }
 9 
10     static class Greet implements IGreet {
11 
12         @Override
13         public void sayHello() {
14             System.out.println("Hello World!");
15         }
16 
17         @Override
18         public void sayHi() {
19             System.out.println("Hi there!");
20         }
21     }
22 
23     static class DynamicProxy implements java.lang.reflect.InvocationHandler {
24 
25         Object originalObj;
26 
27         Object bind(Object originalObj) {
28             this.originalObj = originalObj;
29             return java.lang.reflect.Proxy
30                 .newProxyInstance(originalObj.getClass().getClassLoader(), originalObj.getClass().getInterfaces(), this);
31         }
32 
33         @Override
34         public Object invoke(Object proxy, java.lang.reflect.Method method, Object[] args) throws Throwable {
35             System.out.println("welcome!");
36             return method.invoke(originalObj, args);
37         }
38     }
39 
40     public static void main(String[] args) {
41         System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
42         IGreet greet = (IGreet) new DynamicProxy().bind(new Greet());
43         greet.sayHello();
44         greet.sayHi();
45     }
46 }

編譯 Java 檔案:

javac DomainProxyTest.java

 執行 class 檔案:

 

 發現生成了一個名稱為 $Proxy0.class 的檔案, 該檔案就是 Java 動態生成的代理類. 反編譯看一下其內容:

import DomainProxyTest.IGreet;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

final class $Proxy0 extends Proxy implements IGreet {
    private static Method m1;
    private static Method m3;
    private static Method m4;
    private static Method m2;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void sayHello() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void sayHi() throws  {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    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);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("DomainProxyTest$IGreet").getMethod("sayHello");
            m4 = Class.forName("DomainProxyTest$IGreet").getMethod("sayHi");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

程式碼邏輯很簡單, 呼應了文章上面對動態代理的描述.

 

點選連結加入QQ群: 282575808網際網路技術交流群】:https://jq.qq.com/?_wv=1027&k=Iw86cqY6

相關文章