06、談談 Java 反射機制,動態代理是基於什麼原理?

目錄

1.關於反射

功能:

應用場景:

特點:

2.代理模式

靜態代理:

動態代理:

那我們在開發中怎樣選擇呢?我來簡單對比下兩種方式各自優勢

JDK Proxy 的優勢:

基於類似 cglib 框架的優勢:


1.關於反射

反射最大的作用之一就在於我們可以不在編譯時知道某個對象的類型,而在運行時通過提供完整的”包名+類名.class”得到。注意:不是在編譯時,而是在運行時。

功能:

在運行時能判斷任意一個對象所屬的類。

在運行時能構造任意一個類的對象。

在運行時判斷任意一個類所具有的成員變量和方法。

在運行時調用任意一個對象的方法。

 

說大白話就是,利用Java反射機制我們可以加載一個運行時才得知名稱的class,獲悉其構造方法,並生成其對象實體,能對其方法設值並喚起其methods。

應用場景:

反射技術常用在各類通用框架開發中。因爲爲了保證框架的通用性,需要根據配置文件加載不同的對象或類,並調用不同的方法,這個時候就會用到反射——運行時動態加載需要加載的對象。常見應用:動態代理(AOP、RPC)、提供第三方開發者擴展能力(Servlet容器,JDBC連接)、第三方組件創建對象(DI)……

特點:

由於反射會額外消耗一定的系統資源,因此如果不需要動態地創建一個對象,那麼就不需要用反射。另外,反射調用方法時可以忽略權限檢查,因此可能會破壞封裝性而導致安全問題。

 

2.代理模式

 

靜態代理:

  • 事先寫好代理類,可以手工編寫,也可以用工具生成。
  • 缺點是每個業務類都要對應一個代理類,非常不靈活。

動態代理:

  • 運行時自動生成代理對象。在某些情況下,一個對象不適合或者不能直接引用另一個對象,而代理對象可以在兩者之間起到中介的作用(可類比房屋中介,房東委託中介銷售房屋、簽訂合同等)。
  • 實現階段不用關心代理誰,而是在運行階段才指定代理哪個一個對象(不確定性)。
  • 缺點是生成代理代理對象和調用代理方法都要額外花費時間。

JDK動態代理:

基於Java反射機制實現,必須要實現了接口的業務類才能用這種辦法生成代理對象。新版本也開始結合ASM機制。

採用的JDK提供的動態代理接口InvocationHandler來實現動態代理類。其中invoke方法是該接口定義必須實現的,它完成對真實方法的調用。通過InvocationHandler接口,所有方法都由該Handler來進行處理,即所有被代理的方法都由InvocationHandler接管實際的處理任務。此外,我們常可以在invoke方法實現中增加自定義的邏輯實現,實現對被代理類的業務邏輯無侵入。

下面的 JDK 動態代理例子,非常簡單地實現了動態代理的構建和代理操作。

首先,實現對應的 InvocationHandler;

然後,以接口 Hello 爲紐帶,爲被調用目標構建代理對象;

進而應用程序就可以使用代理對象間接運行調用目標的邏輯,代理爲應用插入額外邏輯(這裏是 println)提供了便利的入口。

public class MyDynamicProxy {
    public static void main(String[] args) {
        HelloImpl hello = new HelloImpl();
        MyInvocationHandler handler = new MyInvocationHandler(hello);
        // 構造代碼實例
        Hello proxyHello = (Hello) Proxy.newProxyInstance(HelloImpl.class.getClassLoader(), HelloImpl.class.getInterfaces(), handler);
        // 調用代理方法
        proxyHello.sayHello();
    }
}

interface Hello {
    void sayHello();
}

class HelloImpl implements Hello {
    @Override
    public void sayHello() {
        System.out.println("Hello World");
    }
}

class MyInvocationHandler implements InvocationHandler {
    private Object target;

    public MyInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        System.out.println("Invoking sayHello");
        Object result = method.invoke(target, args);
        return result;
    }
}

 

cglib動態代理:

基於ASM機制實現,通過生成業務類的子類作爲代理類。

 

那我們在開發中怎樣選擇呢?我來簡單對比下兩種方式各自優勢

JDK Proxy 的優勢:

  1. 最小化依賴關係,減少依賴意味着簡化開發和維護,JDK 本身的支持,可能比 cglib 更加可靠。
  2. 平滑進行 JDK 版本升級,而字節碼類庫通常需要進行更新以保證在新版 Java 上能夠使用。
  3. 代碼實現簡單。

基於類似 cglib 框架的優勢:

  1. 有的時候調用目標可能不便實現額外接口,從某種角度看,限定調用者實現接口是有些侵入性的實踐,類似 cglib 動態代理就沒有這種限制。
  2. 只操作我們關心的類,而不必爲其他相關類增加工作量。
  3. 高性能,速度更快。

我們在選型中,性能未必是唯一考量,可靠性、可維護性、編程工作量等往往是更主要的考慮因素,畢竟標準類庫和反射編程的門檻要低得多,代碼量也是更加可控的,如果我們比較下不同開源項目在動態代理開發上的投入,也能看到這一點。

 

發佈了96 篇原創文章 · 獲贊 19 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章