首頁>技術>

本篇要點介紹觀察者模式和釋出訂閱模式的區別。SpringBoot快速入門事件監聽。什麼是觀察者模式?

觀察者模式是經典行為型設計模式之一。

在GoF的《設計模式》中,觀察者模式的定義:在物件之間定義一個一對多的依賴,當一個物件狀態改變的時候,所有依賴的物件都會自動收到通知。如果你覺得比較抽象,接下來這個例子應該會讓你有所感覺:

就拿使用者註冊功能為例吧,假設使用者註冊成功之後,我們將會發送郵件,優惠券等等操作,很容易就能寫出下面的邏輯:

@RestController@RequestMapping("/user")public class SimpleUserController {    @Autowired    private SimpleEmailService emailService;    @Autowired    private SimpleCouponService couponService;    @Autowired    private SimpleUserService userService;    @GetMapping("/register")    public String register(String username) {        // 註冊        userService.register(username);        // 傳送郵件        emailService.sendEmail(username);        // 傳送優惠券        couponService.addCoupon(username);        return "註冊成功!";    }}

這樣寫會有什麼問題呢?受王爭老師啟發:

方法呼叫時,同步阻塞導致響應變慢,需要非同步非阻塞的解決方案。註冊介面此時做的事情:註冊,發郵件,優惠券,違反單一職責的原則。當然,如果後續沒有拓展和修改的需求,這樣子倒可以接受。如果後續註冊的需求頻繁變更,相應就需要頻繁變更register方法,違反了開閉原則。

針對以上的問題,我們想一想解決的方案:

一、非同步非阻塞的效果可以新開一個執行緒執行耗時的傳送郵件任務,但頻繁地建立和銷燬執行緒比較耗時,並且併發執行緒數無法控制,建立過多的執行緒會導致堆疊溢位。

二、使用執行緒池執行任務解決上述問題。

@Service@Slf4jpublic class SimpleEmailService {	// 啟動一個執行緒執行耗時操作    public void sendEmail(String username) {        Thread thread = new Thread(()->{            try {                // 模擬發郵件耗時操作                Thread.sleep(3000);            } catch (InterruptedException e) {                e.printStackTrace();            }            log.info("給使用者 [{}] 傳送郵件...", username);        });        thread.start();    }}@Slf4j@Servicepublic class SimpleCouponService {    ExecutorService executorService = Executors.newSingleThreadExecutor();	// 執行緒池執行任務,減少資源消耗    public void addCoupon(String username) {        executorService.execute(() -> {            try {                Thread.sleep(3000);            } catch (InterruptedException e) {                e.printStackTrace();            }            log.info("給使用者 [{}] 發放優惠券", username);        });    }}

這裡使用者註冊事件對【傳送簡訊和優惠券】其實是一對多的關係,可以使用觀察者模式進行解耦:

/** * 主題介面 * @author Summerday */public interface Subject {    void registerObserver(Observer observer);    void removeObserver(Observer observer);    void notifyObservers(String message);}/** * 觀察者介面 * @author Summerday */public interface Observer {    void update(String message);}@Component@Slf4jpublic class EmailObserver implements Observer {    @Override    public void update(String message) {        log.info("向[{}]傳送郵件", message);    }}@Component@Slf4jpublic class CouponObserver implements Observer {    @Override    public void update(String message) {        log.info("向[{}]傳送優惠券",message);    }}@Componentpublic class UserRegisterSubject implements Subject {    @Autowired    List<Observer> observers;    @Override    public void registerObserver(Observer observer) {        observers.add(observer);    }    @Override    public void removeObserver(Observer observer) {        observers.remove(observer);    }    @Override    public void notifyObservers(String username) {        for (Observer observer : observers) {            observer.update(username);        }    }}@RestController@RequestMapping("/")public class UserController {    @Autowired    UserRegisterSubject subject;    @Autowired    private SimpleUserService userService;    @GetMapping("/reg")    public String reg(String username) {        userService.register(username);        subject.notifyObservers(username);        return "success";    }}
釋出訂閱模式是什麼?

觀察者模式和釋出訂閱模式是有一點點區別的,區別有以下幾點:

前者:觀察者訂閱主題,主題也維護觀察者的記錄,而後者:釋出者和訂閱者不需要彼此瞭解,而是在訊息佇列或代理的幫助下通訊,實現松耦合。前者主要以同步方式實現,即某個事件發生時,由Subject呼叫所有Observers的對應方法,後者則主要使用訊息佇列非同步實現。

儘管兩者存在差異,但是他們其實在概念上相似,網上說法很多,不需要過於糾結,重點在於我們需要他們為什麼出現,解決了什麼問題。

Spring事件監聽機制概述

SpringBoot中事件監聽機制則透過釋出-訂閱實現,主要包括以下三部分:

事件 ApplicationEvent,繼承JDK的EventObject,可自定義事件。事件釋出者 ApplicationEventPublisher,負責事件釋出。事件監聽者 ApplicationListener,繼承JDK的EventListener,負責監聽指定的事件。

我們透過SpringBoot的方式,能夠很容易實現事件監聽,接下來我們改造一下上面的案例:

SpringBoot事件監聽定義註冊事件
public class UserRegisterEvent extends ApplicationEvent {        private String username;    public UserRegisterEvent(Object source) {        super(source);    }    public UserRegisterEvent(Object source, String username) {        super(source);        this.username = username;    }    public String getUsername() {        return username;    }}
註解方式 @EventListener定義監聽器
/** * 註解方式 @EventListener * @author Summerday */@Service@Slf4jpublic class CouponService {    /**     * 監聽使用者註冊事件,執行發放優惠券邏輯     */    @EventListener    public void addCoupon(UserRegisterEvent event) {        log.info("給使用者[{}]發放優惠券", event.getUsername());    }}
實現ApplicationListener的方式定義監聽器
/** * 實現ApplicationListener<Event>的方式 * @author Summerday */@Service@Slf4jpublic class EmailService implements ApplicationListener<UserRegisterEvent> {    /**     * 監聽使用者註冊事件, 非同步傳送執行傳送郵件邏輯     */    @Override    @Async    public void onApplicationEvent(UserRegisterEvent event) {        log.info("給使用者[{}]傳送郵件", event.getUsername());    }}
註冊事件釋出者
@Service@Slf4jpublic class UserService implements ApplicationEventPublisherAware {    // 注入事件釋出者    private ApplicationEventPublisher applicationEventPublisher;    @Override    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {        this.applicationEventPublisher = applicationEventPublisher;    }    /**     * 釋出事件     */    public void register(String username) {        log.info("執行使用者[{}]的註冊邏輯", username);        applicationEventPublisher.publishEvent(new UserRegisterEvent(this, username));    }}
定義介面
@RestController@RequestMapping("/event")public class UserEventController {    @Autowired    private UserService userService;    @GetMapping("/register")    public String register(String username){        userService.register(username);        return "恭喜註冊成功!";    }}                                                                                                                                                                                                                                                                                                                            
主程式類
@EnableAsync // 開啟非同步@SpringBootApplicationpublic class SpringBootEventListenerApplication {    public static void main(String[] args) {        SpringApplication.run(SpringBootEventListenerApplication.class, args);    }}
測試介面

啟動程式,訪問介面:http://localhost:8081/event/register?username=天喬巴夏,結果如下:

2020-12-21 00:59:46.679  INFO 12800 --- [nio-8081-exec-1] com.hyh.service.UserService              : 執行使用者[天喬巴夏]的註冊邏輯2020-12-21 00:59:46.681  INFO 12800 --- [nio-8081-exec-1] com.hyh.service.CouponService            : 給使用者[天喬巴夏]發放優惠券2020-12-21 00:59:46.689  INFO 12800 --- [         task-1] com.hyh.service.EmailService             : 給使用者[天喬巴夏]傳送郵件

好啦,今天就分享到這裡了!

原文連結:https://www.cnblogs.com/summerday152/p/14166152.html

10
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 大牛帶你進行程式設計師架構修煉:架構設計——高效能設計