Spring Event 觀察者模式, 業務解耦神器

来源:https://www.cnblogs.com/88223100/archive/2023/10/03/Spring-Event-Observer-Mode-Business-Decoupling-Artifact.html
-Advertisement-
Play Games

觀察者模式在實際開發過程中是非常常見的一種設計模式。 Spring Event的原理就是觀察者模式,只不過有Spring的加持,讓我們更加方便的使用這一設計模式。 一、什麼是觀察者模式 概念: 觀察者模式又叫發佈-訂閱模式。 發佈指的是當目標對象的狀態改變時,它就向它所有的觀察者對象發佈狀態更改的消 ...


圖片

觀察者模式在實際開發過程中是非常常見的一種設計模式。

Spring Event的原理就是觀察者模式,只不過有Spring的加持,讓我們更加方便的使用這一設計模式。

一、什麼是觀察者模式

概念: 觀察者模式又叫發佈-訂閱模式。

發佈指的是當目標對象的狀態改變時,它就向它所有的觀察者對象發佈狀態更改的消息,以讓這些觀察者對象知曉。

舉例:

網上有一個非常符合觀察者模式的例子

圖片

當溫度有變化,對應的儀錶盤也會跟著變化。

一個儀錶盤可以當作一個觀察者,去掉一個儀錶盤或者新增一個儀錶盤跟目標對象(溫度)是解耦的,不是強綁定關係。

一句話:感知變化,相應變化


二、觀察者模式 VS 責任鏈模式

這兩種設計模式是有相似的地方,但其實有很大的區別。

我們先來看相似的點,就好比上面的這個例子,我們是不是也可以用責任鏈模式來實現?

當然可以了。

當溫度變化了,一條一條鏈路的執行下去就是了。

圖片

當然如果是我,這個功能在選擇設計模式的時候,我還是會選擇使用觀察者模式。

1、區別

我個人認為主要有四點區別:

「第一點」:我們也會稱觀察者模式為發佈訂閱模式,作為訂閱者來講,每個訂閱者是平級的,也就是每個觀察者對象是平級的,但責任鏈可以有先後次序。

比如我們在電商場景中,有個電商活動,這個商品需要先走 包郵活動->滿減送->會員折扣活動->積分抵扣活動。

這個責任鏈的順序不同會導致最終優惠的價格不同。

「第二點」:所有觀察者一般接收統一參數,但責任鏈獲取的參數可能是上一個鏈路已經處理完成的

就好比上面的電商活動,會員折扣活動計算後的價格,還會傳入到積分抵扣活動中。

「第三點」:觀察者的對象都會執行,但責任鏈這我們可以在得到滿意結果直接返回。

比如我想查一個份數據,這個數據可以先從A -> B -> C,三個介面獲得。只要返回數據,這個鏈路就不用往下走了。

「第四點」:觀察者模式可以做非同步操作,我們說的MQ發佈訂閱模式,就是完全非同步,但是責任鏈不太適合走非同步。


三、代碼示例

1、觀察者模式有哪些角色

抽象被觀察者: 定義了一個介面,包含了註冊觀察者、刪除觀察者、通知觀察者等方法。

具體被觀察者: 實現了抽象被觀察者介面,維護了一個觀察者列表,併在狀態發生改變時通知所有註冊的觀察者。

抽象觀察者: 定義了一個介面,包含了更新狀態的方法。

具體觀察者: 實現了抽象觀察者介面,在被觀察者狀態發生改變時進行相應的處理。

  1. 抽象被觀察者
/**
 * 抽象被觀察者
 */
public interface ISubject {

    /**
     * 新增觀察者
     */
    boolean attach(IObserver observer);

    /**
     * 刪除觀察者
     */
    boolean detach(IObserver observer);

    /**
     * 通知觀察者
     */
    void notify(String event);
}
  1. 抽象觀察者
/**
 *  抽象觀察者
 */
public interface IObserver {
   
    /**
     * 觀察者所執行方法
     */
    void update(String event);
}
  1. 具體被觀察者
/**
 *  具體被觀察者
 */
public class ConcreteSubject implements ISubject {

    private List<IObserver> observers = new ArrayList<>();

    @Override
    public boolean attach(IObserver observer) {
        return this.observers.add(observer);
    }

    @Override
    public boolean detach(IObserver observer) {
        return this.observers.remove(observer);
    }

    @Override
    public void notify(String event) {
        System.out.println("被觀察者: 數據變更 = " + event);
        for (IObserver observer : this.observers) {
             observer.update(event);
        }
    }
}
  1. 具體觀察者
/**
 * 具體觀察者
 */
public class ConcreteObserver implements IObserver {

    @Override
    public void update(String event) {
        System.out.println("觀察者: 收到被觀察者的溫度變動: " + event);
    }
}
  1. 測試
/**
 *  測試
 */
public class ClientTest {
    
    public static void main(String[] args) {
        // 被觀察者
        ISubject subject = new ConcreteSubject();
        // 觀察者
        IObserver observer = new ConcreteObserver();
        // 將觀察者註冊
        subject.attach(observer);
        // 被觀察者通知觀察者
        subject.notify("溫度從6變到7");
    }
}

運行結果

被觀察者: 數據變更 = 溫度從6變到7
觀察者: 收到被觀察者的溫度變動: 溫度從6變到7

當然上面這種模式也太傻了吧,下麵就通過Spring Event實現觀察者模式,非常方便。


四、Spring Event 實現觀察者模式

Spring 基於觀察者模式實現了自身的事件機制,由三部分組成:

事件 ApplicationEvent: 通過繼承它,實現自定義事件。

事件發佈者 ApplicationEventPublisher: 通過它,可以進行事件的發佈。

事件監聽器 ApplicationListener: 通過實現它,進行指定類型的事件的監聽。

這裡以下麵案例實現,當一個用戶出現欠費,那麼通過觀察者模式通過 簡訊通知,郵箱通知,微信通知,到具體用戶

圖片

1、事件 UserArrearsEvent

繼承 ApplicationEvent 類,用戶欠費事件。

/**
 * 用戶欠費事件 繼承ApplicationEvent
 */
public class UserArrearsEvent extends ApplicationEvent {

    /**
     * 用戶名
     */
    private String username;
    
    public UserArrearsEvent(Object source, String username) {
        super(source);
        this.username = username;
    }

    public String getUsername() {
        return username;
    }
}

2、被觀察者 UserArrearsService

/**
 *  被觀察者 實現ApplicationEventPublisherAware 介面
 */
@Service
public class UserArrearsService implements ApplicationEventPublisherAware {

    private Logger logger = LoggerFactory.getLogger(getClass());

    private ApplicationEventPublisher applicationEventPublisher;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    public void arrears(String username) {
        // 執行欠費邏輯
        logger.info("被觀察者 用戶欠費,用戶名稱", username);
        // 發佈
        applicationEventPublisher.publishEvent(new UserArrearsEvent(this, username));
    }

}
  1. 實現 ApplicationEventPublisherAware 介面,從而將 ApplicationEventPublisher 註入到其中。

  2. 在執行完註冊邏輯後,調用 ApplicationEventPublisher的 publishEvent 方法,發佈 UserArrearsEvent 事件。

3、觀察者 EmailService

/**
 *  觀察者 郵箱欠費通知
 */
@Service
public class EmailService implements ApplicationListener<UserArrearsEvent> {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    @Async
    public void onApplicationEvent(UserArrearsEvent event) {
        logger.info("郵箱欠費通知,你好 {} ,請儘快繳費啊啊啊啊!", event.getUsername());
    }
}
  1. 實現 ApplicationListener 介面,通過 E 泛型設置感興趣的事件。

  2. 實現 onApplicationEvent方法,針對監聽的 UserRegisterEvent 事件,進行自定義處理。

  3. 設置 @Async 註解,那就代表走非同步操作。同時需要在啟動類上添加@EnableAsync,這樣非同步才生效。

4、觀察者 SmsService

/**
 *  簡訊欠費通知
 */
@Service
public class SmsService {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @EventListener
    public void smsArrears(UserArrearsEvent event) {
        logger.info("簡訊欠費通知,你好 {} ,請儘快繳費啊啊啊啊!", event.getUsername());
    }
}

這裡提供另一種方式,就是在方法上,添加 @EventListener 註解,並設置監聽的事件為 UserRegisterEvent。

5、介面測試

/**
 *  測試 Sping Event觀察者模式
 */
@RestController
@RequestMapping("/test")
public class DemoController {

    @Autowired
    private UserArrearsService userArrearsService;

    @GetMapping("/arrears")
    public String arrears(String username) {
        userArrearsService.arrears(username);
        return "成功";
    }
}

日誌輸出

被觀察者 用戶欠費,用戶名稱 = 張老三
簡訊欠費通知,你好 張老三 ,請儘快繳費啊啊啊啊!
郵箱欠費通知,你好 張老三 ,請儘快繳費啊啊啊啊!

成功!

GitHub地址:https://github.com/yudiandemingzi/spring-boot-study

 

作者|binron

本文來自博客園,作者:古道輕風,轉載請註明原文鏈接:https://www.cnblogs.com/88223100/p/Spring-Event-Observer-Mode-Business-Decoupling-Artifact.html


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

-Advertisement-
Play Games
更多相關文章
  • 需求:輸入錯誤的手機號,會有提示語,正確的手機號碼會有正確的圖標 效果: 思路: (1)排版(不細講),使用input 、button、span等標簽,排版裡面一個主要的小點是,需要寫出兩個span ,通過v-show先進行隱藏,等後面判斷手機號碼的正確錯誤再進行顯示與隱藏 (2)接著,就需要在in ...
  • Java 21中除了推出JEP 445:Unnamed Classes and Instance Main Methods之外,還有另外一個預覽功能:未命名模式和變數(Unnamed Patterns and Variables)。該新特性的目的是提高代碼的可讀性和可維護性。 下麵通過一個例子來理解 ...
  • Windows 線程同步是指多個線程一同訪問共用資源時,為了避免資源的併發訪問導致數據的不一致或程式崩潰等問題,需要對線程的訪問進行協同和控制,以保證程式的正確性和穩定性。Windows提供了多種線程同步機制,以適應不同的併發編程場景。以上同步機制各有優缺點和適用場景,開發者應根據具體應用場景進行選... ...
  • 很多想學Java的人不知道怎樣選教程,本文對Java自學網站進行評測。 本文不帶主觀傾向,只客觀分析各個網站的區別。 ...
  • 此LIN UDS bootloader的上位機是zFlash, LIN盒子是自己開發的,更新應用程式時bootloader和上位機zFlash間通訊採用UDS協議 ...
  • 索引原理 倒排索引 倒排索引(Inverted Index)也叫反向索引,有反向索引必有正向索引。通俗地來講,正向索引是通過key找value,反向索引則是通過value找key。ES底層在檢索時底層使用的就是倒排索引。 索引模型 現有索引和映射如下: { "products" : { "mappi ...
  • 安裝導入 npm npm i three 導入 並非所有功能都在three,還需從子目錄導入 // three模塊 import * as three from 'three' // 一些不在three模塊的功能,這裡是OrbitControls導入示例 import { OrbitControls ...
  • 大家好,我是王天~ 今天咱們用 reac+reactRouter來實現頁面級的按鈕許可權功能。這篇文章分三部分,實現思路、代碼實現、踩坑記錄。 嫌啰嗦的朋友,直接拖到第二章節看代碼哦。 前言 通常情況下,咱們為用戶添加許可權時,除了頁面許可權,還會細化到按鈕級別,比如、新增、刪除、查看等許可權。 如下效果, ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...