當前位置: 妍妍網 > 碼農

動態代理是基於什麽原理?

2024-03-06碼農

引言:在面試的過程中,經常會有面試官詢問關於動態代理的問題,特別是 JDK 動態代理與 CGLIB 動態代理的區別以及它們各自的套用場景。如果你對動態代理的概念和細節尚未了解,面對這樣的問題可能會感到不安。因此,本文旨在深入探討動態代理的原理和實踐,幫助你掌握這一關鍵知識點,從而在面試中占據有利地位。

題目

動態代理是基於什麽原理?

推薦解析

什麽是動態代理?

動態代理是一種在執行時動態生成代理物件,並在代理物件上進行方法呼叫的編程技術。它主要用於在不修改原有程式碼基礎上,增加或改變某些功能的執行流程。動態代理廣泛套用於 AOP(面向切面編程)、RPC(遠端程序呼叫)、事務管理等領域。在 Java 中,主要有兩種動態代理的實作方式:JDK 動態代理和 CGLIB 動態代理。

JDK 動態代理

JDK 動態代理是基於介面的代理技術。它使用 java.lang.reflect.Proxy 類和 java.lang.reflect.InvocationHandler 介面來建立代理物件。當你呼叫代理物件的任何方法時,呼叫會被轉發到 InvocationHandler invoke 方法。你可以在這個 invoke 方法中定義攔截邏輯,比如前置處理、後置處理等。

為了使用 JDK 動態代理,你的類必須實作一個或多個介面。JDK 動態代理的局限性在於,它只能代理介面方法,如果你有一個類並希望代理其非介面方法,則不能使用 JDK 動態代理。

優點以及缺點

  • 優點 :原生支持,無需引入額外依賴。

  • 缺點 :只能代理介面,如果一個類沒有實作任何介面,則不能使用 JDK 動態代理。

  • 操作步驟

    1)定義一個介面及其實作類。

    2)建立一個實作了 InvocationHandler 介面的類,在該類的 invoke 方法中定義代理邏輯。

    3)透過 Proxy.newProxyInstance 方法動態建立介面的代理物件。

    程式碼範例

    // 定義一個介面
    publicinterfaceHelloService{
    voidsayHello(String name);
    }
    // 實作該介面的類
    public classHelloServiceImplimplementsHelloService{
    @Override
    publicvoidsayHello(String name){
    System.out.println("Hello, " + name);
    }
    }

    //建立一個實作 InvocationHandler 介面的類
    public classHelloServiceHandlerimplementsInvocationHandler{
    //目標物件
    private Object target;
    publicHelloServiceHandler(Object target){
    this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
    System.out.println("Before sayHello");
    // 執行目標物件的方法
    Object result = method.invoke(target, args);
    System.out.println("After sayHello");
    return result;
    }
    }

    //透過 Proxy.newProxyInstance 方法動態建立介面的代理物件
    publicstaticvoidmain(String[] args){
    // 建立目標物件
    HelloService target = new HelloServiceImpl();
    // 建立呼叫處理器物件
    HelloServiceHandler handler = new HelloServiceHandler(target);
    // 建立代理物件
    HelloService proxyInstance = (HelloService) Proxy.newProxyInstance(
    target.get class().get classLoader(),
    target.get class().getInterfaces(),
    handler);
    // 透過代理物件呼叫方法
    proxyInstance.sayHello("World");
    }

    CGLIB 動態代理

    CGLIB(Code Generation Library)是一個強大的、高效能、高品質的 Code 生成庫,它可以在執行時擴充套件 Java 類和實作 Java 介面。不同於 JDK 動態代理,CGLIB 不需要介面,它是透過繼承方式實作代理的。

    CGLIB 底層透過使用一個小而快的字節碼處理框架 ASM ,來轉換字節碼並生成新的類。不僅可以代理普通類的方法,還能代理那些沒有介面的類的方法。

    優點以及缺點

  • 優點 :無需介面實作。在大量呼叫的場景下,其生成的代理物件在呼叫時效能比 JDK 動態代理高

  • 缺點 :對 final 方法無效,需添加額外的依賴。

  • 操作步驟

    1)建立一個需要被代理的類。

    2)建立一個繼承 MethodInterceptor 的代理類,在 intercept 方法中定義代理邏輯。

    3)使用 Enhancer 類建立被代理類的子類別,並設定回呼。

    程式碼範例

    <!--引入 cglib依賴 -->
    <dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
    </dependency>

    //定義一個普通類
    public classHelloService{
    publicvoidsayHello(String name){
    System.out.println("Hello, " + name);
    }
    }

    //建立一個繼承 MethodInterceptor 的代理類 實作 intercept 方法
    public classHelloServiceCglibimplementsMethodInterceptor{
    private Object target;
    public Object getInstance(Object target){
    this.target = target;
    Enhancer enhancer = new Enhancer();
    enhancer.setSuper class(this.target.get class());
    // 設定回呼
    enhancer.setCallback(this);
    // 建立代理物件
    return enhancer.create();
    }
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)throws Throwable {
    System.out.println("Before sayHello");
    // 執行目標類的方法
    Object result = proxy.invokeSuper(obj, args);
    System.out.println("After sayHello");
    return result;
    }
    }

    //實作
    publicstaticvoidmain(String[] args){
    HelloService target = new HelloService();
    HelloServiceCglib cglib = new HelloServiceCglib();
    HelloService proxy = (HelloService) cglib.getInstance(target);
    proxy.sayHello("World");
    }

    原理對比

  • 實作方式 :JDK 動態代理透過反射機制呼叫介面方法,CGLIB 透過生成目標物件的子類別並覆蓋其方法實作代理。

  • 效能 :在呼叫方法的次數非常多的情況下,CGLIB 的效能可能優於 JDK 動態代理,但差異通常不大。CGLIB 初始化的代理物件比 JDK 動態代理慢,因為它需要生成新的類。

  • 使用場景

  • 如果目標物件實作了介面,推薦使用 JDK 動態代理。

  • 如果目標物件沒有實作介面,或者有特定需求需要透過繼承方式代理,則使用 CGLIB。

  • 其他補充

    魚聰明 AI 的回答:

    魚聰明 AI 地址:https://www.yucongming.com/

    靜態代理知識補充

    靜態代理是一種設計模式,它在程式執行前就已經存在代理類的程式碼,代理類和目標物件實作相同的介面或繼承相同的父類。透過代理類來間接存取目標物件,從而在不修改目標物件程式碼的情況下,增加或改變某些功能的執行流程。靜態代理通常用於控制對目標物件的存取,或在呼叫目標物件的方法前後添加額外的功能,如安全檢查、事務處理、日誌記錄等。

    靜態代理的特點

  • 編譯時增加功能 :靜態代理的實作在編譯階段就已經完成,所有的增強功能都需要在代理類中顯式編寫。

  • 程式碼冗余 :如果有多個類需要代理,每個類都需要一個對應的代理類,這會導致大量的重復程式碼。

  • 緊耦合 :代理類和目標物件之間的關系在編譯時就已經確定,增加或修改代理類需要重新編譯。

  • 靜態代理的實作

    靜態代理的實作涉及到四個角色:

    1) 介面( Interface ) :定義了目標物件和代理物件共同遵循的操作集合。

    2) 目標物件( Target Object ) :實作了介面的類,定義了要執行的具體操作。

    3) 代理物件( Proxy Object ) :同樣實作了介面,用於包裝目標物件,可以在呼叫目標物件的方法前後執行一些附加操作。

    4) 客戶端( Client ) :使用代理物件的使用者。

    簡單實作

    假設有一個簡單的介面和實作類,介面定義了一個 sayHello 方法:

    // 定義介面
    publicinterfaceHelloService{
    voidsayHello(String name);
    }
    // 實作介面的目標類
    public classHelloServiceImplimplementsHelloService{
    @Override
    publicvoidsayHello(String name){
    System.out.println("Hello, " + name);
    }
    }

    // 靜態代理類 建立一個代理類來增強 sayHello 方法:
    public classHelloServiceProxyimplementsHelloService{
    private HelloService helloService;
    publicHelloServiceProxy(HelloService helloService){
    this.helloService = helloService;
    }
    @Override
    publicvoidsayHello(String name){
    System.out.println("Before sayHello"); // 前置增強
    helloService.sayHello(name);
    System.out.println("After sayHello"); // 後置增強
    }
    }

    //實作
    publicstaticvoidmain(String[] args){
    HelloService helloService = new HelloServiceImpl();
    HelloService proxy = new HelloServiceProxy(helloService);
    proxy.sayHello("World");
    }

    推薦文章和書籍

    文章:https://zhuanlan.zhihu.com/p/86293659

    書籍:【 Java 核心技術卷 I 】

    歡迎交流

    在閱讀完本文後,你應該對 Java 中的動態代理機制,包括 JDK 動態代理和 CGLIB 動態代理,有了深入的理解。掌握了這些代理技術,你一定能在討論 Java 設計模式、面向切面編程( AOP )以及中介軟體開發時,與面試官或同行進行深入的交流。本文最後提出三個問題,歡迎大家在評論區分享觀點,以促進共同進步:

    1)在哪些場景下應該優先考慮使用 JDK 動態代理和 CGLIB 動態代理,以最佳化程式結構或增強功能?

    2)如何合理選擇和套用 JDK 動態代理與 CGLIB 動態代理,以構建靈活且高效的 AOP 框架或中介軟體?

    3)JDK 動態代理和 CGLIB 動態代理在效能、適用場景和易用性方面有何不同,這些差異如何影響套用的設計和選擇?

    這些問題旨在深化對 Java 動態代理技術及其在實際套用中的作用的理解,期待你的參與和貢獻。


    👇🏻 點選下方閱讀原文,獲取魚皮往期編程幹貨

    往期推薦