天天看點

Spring 中的觀察者模式

一、Spring 中觀察者模式的四個角色

1. 事件(ApplicationEvent)

ApplicationEvent 是所有事件對象的父類。ApplicationEvent 繼承自 jdk 的 EventObject, 所有的事件都需要繼承 ApplicationEvent, 并且通過 source 得到事件源。

下列描述了Spring提供的内置事件:

  • ContextRefreshedEvent:事件釋出在 ApplicationContext 初始化或重新整理時(例如:通過在 ConfigurableApplicationContext 接口使用refresh()方法)。這裡,“初始化”意味着所有 bean 加載,post-processor bean 被檢測到并且激活,單例預先執行個體化,ApplicationContext 對象可以使用了。隻要上下文沒有關閉,可以觸發多次重新整理, ApplicationContext 提供了一種可選擇的支援這種“熱”重新整理。例如:XmlWebApplicationContext 支援熱重新整理,但 GenericApplicationContext 并非如此。具體是在 AbstractApplicationContext 的 finishRefresh() 方法中。
  • ContextStartedEvent:事件釋出在 ApplicationContext 開始使用 ConfigurableApplicationContext 接口 start() 方法。這裡,“開始”意味着所有生命周期 bean 接收到一個明确的起始信号。通常,這個信号用于明确停止後重新啟動,但它也可以用于啟動元件沒有被配置為自動運作(例如:元件還沒有開始初始化)。
  • ContextStoppedEvent:事件釋出在 ApplicationContext 停止時通過使用 ConfigurableApplicationContext 接口上的 stop() 方法。在這裡,“停止”意味着所有生命周期bean接收一個顯式的停止信号。停止上下文可以通過重新調用start()方法。
  • ContextClosedEvent:事件釋出在 ApplicationContext 關閉時通過關閉 ConfigurableApplicationContext 接口()方法。這裡,“封閉”意味着所有單例 bean 被摧毀。一個封閉的環境達到生命的終結。它不能重新整理或重新開機。
  • RequestHandledEvent:一個特定的web事件告訴所有能處理HTTP請求的bean 。這個事件是在請求完成後釋出的。這個事件隻适用于使用 Spring 的 DispatcherServlet 的web應用程式。

2. 事件監聽(ApplicationListener)

ApplicationListener 事件監聽器,也就是觀察者。繼承自 jdk 的 EventListener,該類中隻有一個方法 onApplicationEvent。當監聽的事件發生後該方法會被執行。

3. 事件釋出(ApplicationContext)

ApplicationContext 是 Spring 中的核心容器,在事件監聽中 ApplicationContext 可以作為事件的釋出者,也就是事件源。因為 ApplicationContext 繼承自 ApplicationEventPublisher。在 ApplicationEventPublisher 中定義了事件釋出的方法 — publishEvent(Object event)

4. 事件管理(ApplicationEventMulticaster)

ApplicationEventMulticaster 用于事件監聽器的注冊和事件的廣播。監聽器的注冊就是通過它來實作的,它的作用是把 Applicationcontext 釋出的 Event 廣播給它的監聽器清單。

二、Spring中實作觀察者模式

  1. 自定義需要釋出的事件類,需要繼承 ApplicationEvent 類或 PayloadApplicationEvent (該類也僅僅是對 ApplicationEvent 的一層封裝)
  2. 使用 @EventListener 來監聽事件或者實作 ApplicationListener 接口。
  3. 使用 ApplicationEventPublisher 來釋出自定義事件(@Autowired注入即可)

@TransactionalEventListener 監聽器:如果事件的釋出不是在事務(@Transactional)範圍内,則監聽不到該事件,除非将 fallbackExecution 标志設定為 true:

@TransactionalEventListener(fallbackExecution = true)
           

如果在事務中,可以選擇在事務的哪個階段來監聽事件,預設在事務送出後監聽:

@TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)
           

以上介紹的事件監聽都是同步,如果需要開啟異步支援的話:

@Configuration
@EnableAsync
public class AsyncEventConfiguration implements AsyncConfigurer {
    @Override
    public Executor getAsyncExecutor() {
        return Executors.newCachedThreadPool();
    }
}
           

三、 實戰

事件(MyEvent.java)

@Component
public class MyEvent extends ApplicationEvent {


    public MyEvent(ApplicationContext source) {
        super(source);
        System.out.println("MyEvent 構造器執行");
    }

    public void echo() {
        System.out.println("模拟業務邏輯執行");
    }
}
           

事件監聽(MyListenerA.java、MyListenerB.java)

@Component
public class MyListenerA implements ApplicationListener<MyEvent> {


    @Override
    public void onApplicationEvent(MyEvent myEvent) {
        System.out.println("MyListenerA");
        myEvent.echo();
    }
}
           
@Component
public class MyListenerB {
    
    @EventListener
    public void onApplicationEvent(MyEvent myEvent) {
        System.out.println("MyListenerB");
        myEvent.echo();
    }
    
}
           

事件釋出(MyPublisher.java)

@Component
public class MyPublisher implements ApplicationContextAware {

    private ApplicationContext applicationContext;


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    /**
     * 釋出事件
     * 監聽該事件的監聽者都可以擷取消息
     *
     * @param myEvent
     */
    public void publisherEvent(MyEvent myEvent) {
        System.out.println("---開始釋出 myEvent 事件---");
        applicationContext.publishEvent(myEvent);
    }
}
           

單元測試(ApplicationTests.java)

@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTests {

    @Autowired
    private MyPublisher myPublisher;
    @Autowired
    private MyEvent myEvent;

    @Test
    public void contextLoads() {
        myPublisher.publisherEvent(myEvent);
    }
}
           

示範源代碼 :https://github.com/JMCuixy/design-patterns/tree/master/src/main/java/com/example/observer/spring