設計模式:代理模式是什麼,Spring AOP還和它有關係?

来源:https://www.cnblogs.com/yeya/archive/2019/07/15/11169014.html
-Advertisement-
Play Games

接著學習設計模式系列,今天講解的是代理模式。 定義 什麼是代理模式? 代理模式,也叫委托模式,其定義是給某一個對象提供一個代理對象,並由代理對象控制對原對象的引用。它包含了三個角色: Subject:抽象主題角色。可以是抽象類也可以是介面,是一個最普通的業務類型定義。 RealSubject:具體主 ...


接著學習設計模式系列,今天講解的是代理模式。

定義

什麼是代理模式?

代理模式,也叫委托模式,其定義是給某一個對象提供一個代理對象,並由代理對象控制對原對象的引用。它包含了三個角色:

Subject:抽象主題角色。可以是抽象類也可以是介面,是一個最普通的業務類型定義。

RealSubject:具體主題角色,也就是被代理的對象,是業務邏輯的具體執行者。

Proxy:代理主題角色。負責讀具體主題角色的引用,通過真實角色的業務邏輯方法來實現抽象方法,併在前後可以附加自己的操作。

用類圖來表示的話大概如下:

我們可以用舉一個電影演員拍戲的例子,一般來說,演員最主要的工作就是演戲,其他的事可以交給他的經紀人去做,例如談合同,安排檔期等等,而負責這些場外工作的經紀人就相當於Proxy,而負責核心業務的演員就是 RealSubject

這就是代理模式的設計思路,除此之外,代理模式分為靜態代理和動態代理,靜態代理是我們自己創建一個代理類,而動態代理是程式自動幫我們生成一個代理類,可以在程式運行時再生成對象,下麵分別對它們做介紹。

靜態代理

靜態代理在程式運行之前,代理類.class文件就已經被創建了。還是用上面演員演戲的例子,在靜態代理模式中,我們要先創建一個抽象主題角色 Star

public interface Star {
    // 演戲
    void act();
}

接下來就是創建具體的主題角色和代理主題角色,分別實現這個介面,先創建一個具體的主題角色 Actor

/**
 * 演員,也就是具體的主題角色
 *
 * @author Tao
 * @since 2019/7/9 18:34
 */
public class Actor implements Star {
    public void act() {
        System.out.println("演員演戲~~~");
    }
}

然後就是創建代理主題角色,也就是代理類,代理類本身並不負責核心業務的執行流程,演戲這事還得明星自己來。所以在代理類中需要將真實對象引入,下麵是具體的代碼實現:

/**
 * 代理對象
 * @author Tao
 * @since 2019/7/9 18:43
 */
public class Agent implements Star {
    /**
     * 接收真實的明星對象
     */
    private Star star;

    /**
     * 通過構造方法傳進來真實的明星對象
     *
     * @param star star
     */
    public Agent(Star star) {
        this.star = star;
    }

    public void act() {
        System.out.println("簽合同");
        star.act();
        System.out.println("演完戲就收錢了");
    }
}

代碼的邏輯還是比較清晰的,通過維護一個Star對象,可以在act里調用具體主題角色的業務邏輯,並且在核心邏輯前後可以做一些輔助操作,比如簽合同,收錢等,這樣代理模式的角色就都分工完成了,最後用一個場景類來驗證下:

public class Client {
    public static void main(String[] args) {
        Star actor = new Actor();
        Agent agent = new Agent(actor);
        agent.act();
    }
}

運行的結果如下:

簽合同
演員演戲~~~
演完戲就收錢了

動態代理

動態代理分為兩種,分別是JDK動態代理和 CGLIB 動態代理,怎麼又分了,代理模式分類真多,不過來都來了,就都學習一下吧。

JDK動態代理

前面說了,在動態代理中我們不再需要再手動的創建代理類,我們只需要編寫一個動態處理器就可以了。真正的代理對象由JDK再運行時幫我們動態的來創建。

/**
 * 動態代理處理類
 *
 * @author Tao
 * @since 2019/7/9 19:04
 */
public class JdkProxyHandler {

    /**
     * 用來接收真實明星對象
     */
    private Object star;

    /**
     * 通過構造方法傳進來真實的明星對象
     *
     * @param star star
     */
    public JdkProxyHandler(Star star) {
        super();
        this.star = star;
    }

    /**
     * 給真實對象生成一個代理對象實例
     *
     * @return Object
     */
    public Object getProxyInstance() {
        return Proxy.newProxyInstance(star.getClass().getClassLoader(),
                star.getClass().getInterfaces(), (proxy, method, args) -> {

                    System.out.println("簽合同");
                    // 執行具體的業務邏輯
                    Object object = method.invoke(star, args);
                    System.out.println("演出完經紀人去收錢……");

                    return object;
                });
    }
}

這裡說一下Proxy.newProxyInstance 這個方法,該方法包含了三個參數,

  • ClassLoader loader:指定當前目標對象使用的類載入器,獲取載入器的方法是固定的;
  • Class<?>[] interfaces:指定目標對象實現的介面的類型,使用泛型方式確認類型;
  • InvocationHandler:指定動態處理器,執行目標對象的方法時會觸發事件處理器的方法。

寫完了動態代理實現類,我們寫個場景類測試下,

public class Client {
    public static void main(String[] args) {
        Star actor = new Actor();
        // 創建動態代理對象實例
        Star jdkProxy = (Star) new JdkProxyHandler(actor).getProxyInstance();
        jdkProxy.act();
    }
}

執行結果正常輸出:

簽合同
演員演戲~~~
演出完代理去收錢……

由此可見,JDK 動態代理確實發揮了代理的功能,相對於靜態代理,JDK 動態代理大大減少了我們的開發任務,同時減少了對業務介面的依賴,降低了耦合度。但它同樣有缺陷,就是動態代理的實現類需要類實現介面來完成代理的業務,也就是說它始終無法擺脫僅支持interface代理的桎梏,這是設計上的缺陷。而這時CGLIB 動態代理就派上用場了。

CGLIB 動態代理

CGLib採用了非常底層的位元組碼技術,其原理是通過位元組碼技術為一個類創建子類,併在子類中採用方法攔截的技術攔截所有父類方法的調用,順勢織入橫切邏輯。但因為採用的是繼承,所以不能對final修飾的類進行代理。下麵我們寫一個關於CGLib的動態代理類,值得說下的是,CGLib所在的依賴包不是JDK本身就有的,所以我們需要額外引入,如果是用maven來管理的話,就可以直接引入如下的依賴:

<dependencies>
    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.2.3</version>
    </dependency>
</dependencies>

使用 CGLIB 需要實現 MethodInterceptor 介面,並重寫intercept 方法,在該方法中對原始要執行的方法前後做增強處理。該類的代理對象可以使用代碼中的位元組碼增強器來獲取。具體的代碼如下:

public class CglibProxy implements MethodInterceptor {
    /**
     * 維護目標對象
     */
    private Object target;

    public Object getProxyInstance(final Object target) {
        this.target = target;
        // Enhancer類是CGLIB中的一個位元組碼增強器,它可以方便的對你想要處理的類進行擴展
        Enhancer enhancer = new Enhancer();
        // 將被代理的對象設置成父類
        enhancer.setSuperclass(this.target.getClass());
        // 回調方法,設置攔截器
        enhancer.setCallback(this);
        // 動態創建一個代理類
        return enhancer.create();
    }

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("簽合同");
        // 執行具體的業務邏輯
        Object result = methodProxy.invoke(o, objects);
        System.out.println("演出完經紀人去收錢……");
        return result;
    }
}

場景測試類:
public class Client {
    public static void main(String[] args) {
        Star actor = new Actor();
        // 創建動態代理對象實例
        Star proxy = (Star) new CglibProxy().getProxyInstance(actor);
        proxy.act();
    }
}

可以看出,測試類的邏輯和JDK動態代理差不多,其實套路都是一樣的,其實技術實現不同。

總結一下CGLIB代理模式: CGLIB創建的動態代理對象比JDK創建的動態代理對象的性能更高,但是CGLIB創建代理對象時所花費的時間卻比JDK多得多。所以對於單例的對象,因為無需頻繁創建對象,用CGLIB合適,反之使用JDK方式要更為合適一些。同時由於CGLib由於是採用動態創建子類的方法,對於final修飾的方法無法進行代理。

擴展知識

這裡擴展一個知識點,那就是Spring AOP的底層實現,為什麼在這裡提及呢?因為Spring AOP的底層實現就是基於代理模式,而JDK 動態代理和 CGLIB 動態代理均是實現 Spring AOP 的基礎。我們可以看下AOP的部分底層源碼:

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {

    @Override
    public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
        if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
            Class<?> targetClass = config.getTargetClass();
            if (targetClass == null) {
                throw new AopConfigException("TargetSource cannot determine target class: " +
                        "Either an interface or a target is required for proxy creation.");
            }
            // 判斷目標類是否是介面或者目標類是否Proxy類型,若是則使用JDK動態代理
            if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
                return new JdkDynamicAopProxy(config);
            }
            // 使用CGLIB的方式創建代理對象
            return new ObjenesisCglibAopProxy(config);
        }
        else {
            // 上麵條件都不滿足就使用JDK的提供的代理方式生成代理對象
            return new JdkDynamicAopProxy(config);
        }
    }
}

源碼的判斷邏輯並不難,主要是根據目標類是否是介面或者Proxy類型來判斷使用哪種代理模式創建代理對象,使用的代理模式正是JDK動態代理和CGLIB 動態代理技術。由此可見,瞭解代理模式還是很重要的,起碼以後面試官問AOP的底層實現時,我們還能吹一波呢,哈哈~~~


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 摘要: 記錄用戶行為,排查線上BUG。 作者:一步一個腳印一個坑 原文: "如何定位前端線上問題(如何排查前端生產問題)" "Fundebug" 經授權轉載,版權歸原作者所有。 一直以來,前端的線上問題很難定位,因為它發生於用戶的一系列操作之後。錯誤的原因可能源於機型,網路環境,複雜的操作行為等等, ...
  • 1.ul{ list-style-type:none;} ...
  • 1.去掉下劃線 text-decoration:none; ...
  • 利用 Bootstrap modal 模態框彈層添加或編輯數據,第二次彈出模態框時總是記住上一次的數據值,stackoverflow 上找到個比較好的方法,就是利用 jQuery 的 clone 方法,具體代碼如下 : ...
  • 一、開閉原則(是面向對象開發中最基礎的原則,它指導建立更加穩定靈活的系統) 開閉原則是對擴展和修改行為的一個原則,指的是軟體中的函數、類、模塊應該對擴展開放,對修改關閉。強調的是用抽象構建框架,用實現擴展細節。常用於解決的問題如:更新版本時,儘量在不修改源代碼,但增加新功能。 二、依賴倒置 依賴倒置 ...
  • 來自:Python編程與實戰(微信號:pthon1024),作者:Jerryning 沒有辦法轉,整個複製下來了 本文要點 字元串拼接 拆分含有多種分隔符的字元串 判讀字元串a是否以字元串b開頭或結尾 調整字元串中文本的格式 對字元串進行左,右,居中對齊 刪除字元串中不需要的字元 字元串拼接 拆分含 ...
  • Spring Cloud Alibaba | 序言 @[TOC] 1. Spring Cloud Alibaba是什麼? Spring Cloud Alibaba 致力於提供微服務開發的一站式解決方案。此項目包含開發分散式應用微服務的必需組件,方便開發者通過 Spring Cloud 編程模型輕鬆使 ...
  • 我們分析軟體項目開發過程中各階段的時間占比可以慢慢體會到,一套軟體項目 UI及基本業務的設計會占到整個項目開發的50%以上的時間,再看UI設計,通用的部分占到70%以上,我們會想,如果能將這些重覆簡易的工作能否通過簡易的操作來快速實現呢? 這樣可以將大部分的時間用到後續的真實業務邏輯處理上去,就可以 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...