別再面向for循環(huán)編程了,Spring自帶的觀察者模式就很香!
分享下如何在 Spring/ Spring Boot 中實現(xiàn)觀察者模式。
不用再面試 for 循環(huán)編程了,Spring 框架自帶的事件監(jiān)聽機制,實現(xiàn)觀察者模式、實現(xiàn)解耦輕松幫你全搞定!
Spring 事件監(jiān)聽機制
其實在 Spring/ Spring Boot 框架中有一套事件監(jiān)聽機制,可以實現(xiàn)觀察者模式。
Spring/ Spring Boot 框架中也都內(nèi)置了許多事件,我們也可以自定義發(fā)布應(yīng)用程序事件,下面我們會介紹。
其主要涉及到的幾個核心類和接口如下 :
ApplicationEvent
ApplicationEvent(應(yīng)用程序事件)它是一個抽象類,相當(dāng)于觀察者模式中的觀察目標。
ApplicationEvent 源碼如下:
- public abstract class ApplicationEvent extends EventObject {
- /** use serialVersionUID from Spring 1.2 for interoperability. */
- private static final long serialVersionUID = 7099057708183571937L;
- /** System time when the event happened. */
- private final long timestamp;
- /**
- * Create a new {@code ApplicationEvent}.
- * @param source the object on which the event initially occurred or with
- * which the event is associated (never {@code null})
- */
- public ApplicationEvent(Object source) {
- super(source);
- this.timestamp = System.currentTimeMillis();
- }
- /**
- * Return the system time in milliseconds when the event occurred.
- */
- public final long getTimestamp() {
- return this.timestamp;
- }
- }
ApplicationEvent 繼承自 Java 中的 EventObject 事件對象類,Spring 框架中的所有事件都繼承自 ApplicationEvent 類,它是所有事件的父類。
ApplicationEvent 主要的核心是類構(gòu)造器,它可以初始化一個 source 事件關(guān)聯(lián)對象,以便在事件監(jiān)聽器中獲取并通知更新。
ApplicationListener
ApplicationListener(應(yīng)用程序事件監(jiān)聽器)它是一個接口,相當(dāng)于觀察者模式中的觀察者。
ApplicationListener 源碼如下:
- public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
- /**
- * Handle an application event.
- * @param event the event to respond to
- */
- void onApplicationEvent(E event);
- }
ApplicationListener 繼承自 Java 中的 EventListener 事件監(jiān)聽接口,ApplicationListener 類中只有一個 onApplicationEvent 方法,當(dāng)指定監(jiān)聽的事件被發(fā)布后就會被觸發(fā)執(zhí)行,可以通過 event 獲取事件中的關(guān)聯(lián)對象。
ApplicationEventPublisher
應(yīng)用程序事件發(fā)布接口,封裝了事件發(fā)布功能的基礎(chǔ)接口。
- public interface ApplicationEventPublisher {
- /**
- * Notify all <strong>matching</strong> listeners registered with this
- * application of an application event. Events may be framework events
- * (such as ContextRefreshedEvent) or application-specific events.
- * <p>Such an event publication step is effectively a hand-off to the
- * multicaster and does not imply synchronous/asynchronous execution
- * or even immediate execution at all. Event listeners are encouraged
- * to be as efficient as possible, individually using asynchronous
- * execution for longer-running and potentially blocking operations.
- * @param event the event to publish
- * @see #publishEvent(Object)
- * @see org.springframework.context.event.ContextRefreshedEvent
- * @see org.springframework.context.event.ContextClosedEvent
- */
- default void publishEvent(ApplicationEvent event) {
- publishEvent((Object) event);
- }
- /**
- * Notify all <strong>matching</strong> listeners registered with this
- * application of an event.
- * <p>If the specified {@code event} is not an {@link ApplicationEvent},
- * it is wrapped in a {@link PayloadApplicationEvent}.
- * <p>Such an event publication step is effectively a hand-off to the
- * multicaster and does not imply synchronous/asynchronous execution
- * or even immediate execution at all. Event listeners are encouraged
- * to be as efficient as possible, individually using asynchronous
- * execution for longer-running and potentially blocking operations.
- * @param event the event to publish
- * @since 4.2
- * @see #publishEvent(ApplicationEvent)
- * @see PayloadApplicationEvent
- */
- void publishEvent(Object event);
- }
ApplicationEventPublisher 有一個默認接口方法和接口方法,接口方法需要由具體的子類容器實現(xiàn)。
ApplicationContext
ApplicationContext 這個類就再熟悉不過了,它是 Spring 框架中的核心容器。
如下圖所示,ApplicationContext 接口繼承了 ApplicationEventPublisher 接口,所以常用的 ApplicationContext 就可以用來發(fā)布事件。
以上介紹的 Spring 事件監(jiān)聽發(fā)布角色串起來就是,通過 ApplicationEventPublisher 或者 ApplicationContext 容器發(fā)布 ApplicationEvent 事件并關(guān)聯(lián)事件對象,然后 ApplicationListener 監(jiān)聽該事件,當(dāng)事件發(fā)布后,監(jiān)聽器就會收執(zhí)行并獲取到事件及關(guān)聯(lián)對象。
Spring Boot 觀察者模式實戰(zhàn)
搞懂了 Spring 框架中的事件和監(jiān)聽機制,那我們還是以上篇中觀察者模式的例子來改造下。
Spring Boot 基礎(chǔ)性的知識和搭建過程就不介紹了,不熟悉的可以關(guān)注公眾號Java技術(shù)棧,在后臺回復(fù)關(guān)鍵字 "boot" 閱讀我之前寫的系列教程。
所有 Spring Boot 教程實戰(zhàn)源碼在下面?zhèn)€倉庫:
https://github.com/javastacks/spring-boot-best-practice
新增觀察者目標類
- import lombok.Getter;
- import org.springframework.context.ApplicationEvent;
- /**
- * 觀察目標:棧長
- * 來源微信公眾號:Java技術(shù)棧
- */
- @Getter
- public class JavaStackEvent extends ApplicationEvent {
- /**
- * Create a new {@code ApplicationEvent}.
- *
- * @param source the object on which the event initially occurred or with
- * which the event is associated (never {@code null})
- */
- public JavaStackEvent(Object source) {
- super(source);
- }
- }
實現(xiàn) Spring 框架中的 ApplicationEvent 應(yīng)用程序事件接口,相當(dāng)于是一個觀察者目標。
新增觀察者類
- import lombok.NonNull;
- import lombok.RequiredArgsConstructor;
- import org.springframework.context.ApplicationListener;
- import org.springframework.scheduling.annotation.Async;
- /**
- * 觀察者:讀者粉絲
- * 來源微信公眾號:Java技術(shù)棧
- */
- @RequiredArgsConstructor
- public class ReaderListener implements ApplicationListener<JavaStackEvent> {
- @NonNull
- private String name;
- private String article;
- @Async
- @Override
- public void onApplicationEvent(JavaStackEvent event) {
- // 更新文章
- updateArticle(event);
- }
- private void updateArticle(JavaStackEvent event) {
- this.article = (String) event.getSource();
- System.out.printf("我是讀者:%s,文章已更新為:%s\n", this.name, this.article);
- }
- }
實現(xiàn) Spring 框架中的 ApplicationListener 應(yīng)用監(jiān)聽接口,相當(dāng)于是觀察者。
觀察目標和觀察者類結(jié)構(gòu)圖如下:
新增測試配置類
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.boot.CommandLineRunner;
- import org.springframework.context.ApplicationContext;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- @Slf4j
- @Configuration
- public class ObserverConfiguration {
- @Bean
- public CommandLineRunner commandLineRunner(ApplicationContext context) {
- return (args) -> {
- log.info("發(fā)布事件:什么是觀察者模式?");
- context.publishEvent(new JavaStackEvent("什么是觀察者模式?"));
- };
- }
- @Bean
- public ReaderListener readerListener1(){
- return new ReaderListener("小明");
- }
- @Bean
- public ReaderListener readerListener2(){
- return new ReaderListener("小張");
- }
- @Bean
- public ReaderListener readerListener3(){
- return new ReaderListener("小愛");
- }
- }
在 Spring 配置中創(chuàng)建了三個讀者 Bean,在 Spring Boot 啟動后發(fā)布一個觀察者模式事件,然后這三個 Bean 就會收到通知。
輸出結(jié)果:
這里每個讀者創(chuàng)建一個 Bean 可能不太合適,因為要模仿上一個觀察者模式的應(yīng)用。
實際中的觀察者模式應(yīng)用應(yīng)該是指具體業(yè)務(wù),舉例說一個電商支付場景,在用戶支付完后可以發(fā)布一個支付事件,然后會有扣減積分,短信通知、贈送優(yōu)惠券等一系列后續(xù)的事件監(jiān)聽器觀察者,這樣可以實現(xiàn)業(yè)務(wù)解耦,這是一種很典型的應(yīng)用場景。
如果大家有用到消息中間件,其實也是觀察者模式中發(fā)布訂閱模式的概念。
總結(jié)
利用 Spring 中的事件監(jiān)聽機制也可以輕松實現(xiàn)觀察者模式,觀察目標也不需要維護觀察者列表了,相當(dāng)于發(fā)布-訂閱模式,它們之間是完全解耦的,但每個觀察者需要創(chuàng)建一個 Bean。
好了,今天的分享就到這里了,又學(xué)了一種設(shè)計模式的寫法吧,后面棧長我會更新其他設(shè)計模式的實戰(zhàn)文章,公眾號Java技術(shù)棧第一時間推送。
本節(jié)教程所有實戰(zhàn)源碼已上傳到這個倉庫:
https://github.com/javastacks/spring-boot-best-practice