Spring Event事件通知機制 源碼學習

筆記簡述
本學習筆記主要是介紹Spring中的事件通知是如何實現的,同步和異步事件通知的用法和實現細節以及Spring提供的常見的Event,如果實際開發中需要根據事件推送完成相應的功能,該如何選擇Event
Spring更多可查看Spring 源碼學習

目錄

Spring Event事件通知機制 源碼學習
1、監聽者模式
2、DEMO(同步)
3、Spring實現細節
4、Spring Event
4.1 ContextRefreshedEvent
4.2 ServletRequestHandledEvent
5、異步Pushlish以及DEMO

1、監聽者模式

學習spring的事件通知機制肯定要先了解監聽者模式(監聽者模式和觀察者模式有什么區別?

監聽者模式包含了一個監聽者Listener與之對應的事件Event,還有一個事件發布者EventPublish,過程就是EventPublish發布一個事件,被監聽者捕獲到,然后執行事件相應的方法

觀察者模式是一對多的模式,一個被觀察者Observable和多個觀察者Observer,被觀察者中存儲了所有的觀察者對象,當被觀察者接收到一個外界的消息,就會遍歷廣播推算消息給所有的觀察者
例如日常生活中的訂閱報紙,報紙老板A,現在小明和老板打招呼說我要訂報紙(這個過程就相當于觀察者的注冊),老板A就會拿出自己的小本本記下小明,下次小王、小芳也是類似的操作,現在老板A就有了三個觀察者了,然后老板會自動的把報紙送到三位的家里,突然有一天小明說不想訂報紙了,老板就在自己的小本本上劃掉小明的名字(觀察者的取消注冊),等到送報紙的時候就不再送到小明家里。

2、DEMO(同步)

事件定義

public class EventDemo extends ApplicationEvent {
    private String message;
    public EventDemo(Object source, String message) {
        super(source);
        this.message = message;
    }

    public String getMessage() {
        return message;
    }
}

事件監聽者

@Component
public class EventDemoListern implements ApplicationListener<EventDemo> {

    @Override
    public void onApplicationEvent(EventDemo event) {
        System.out.println("receiver " + event.getMessage());
    }
}

事件發布

@Component
public class EventDemoPublish {

    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    public void publish(String message){
        EventDemo demo = new EventDemo(this, message);
        applicationEventPublisher.publishEvent(demo);
    }

}

最后獲取到EventDemoPublish這個bean,直接publish就可以完成操作了。

3、Spring實現細節

在spring中由于類的細節太多,參數方法也非常的多,不太建議通篇的一個一個看,優先關注我們關注的點,然后打斷點的方式具體了解需要的參數和必備的方法等

AbstractApplicationContext類就有publishEvent()方法,先分析下。

protected void publishEvent(Object event, ResolvableType eventType) {
    Assert.notNull(event, "Event must not be null");
    if (logger.isTraceEnabled()) {
        logger.trace("Publishing event in " + getDisplayName() + ": " + event);
    }

    ApplicationEvent applicationEvent;
    if (event instanceof ApplicationEvent) {
           // 如果是ApplicationEvent對象
        applicationEvent = (ApplicationEvent) event;
    }
    else {
        applicationEvent = new PayloadApplicationEvent<Object>(this, event);
        if (eventType == null) {
            eventType = ((PayloadApplicationEvent)applicationEvent).getResolvableType();
        }
        // 否則就包裝為PayloadApplicationEvent時間,并獲取對應的事件類型
    }

    if (this.earlyApplicationEvents != null) {
           //初始化時候的事件容器,默認為null的
        this.earlyApplicationEvents.add(applicationEvent);
    }
    else {
        getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
        //  監聽者容器?大概的就這個意思,推送消息給監聽器
    }

    // 如果當前命名空間還有父親節點,也需要給父親推送該消息
    if (this.parent != null) {
        if (this.parent instanceof AbstractApplicationContext) {
            ((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
        }
        else {
            this.parent.publishEvent(event);
        }
    }
}

ApplicationEventMulticaster getApplicationEventMulticaster() throws IllegalStateException {
    if (this.applicationEventMulticaster == null) {
           // 意味著肯定需要提前初始化這個監聽器容器
        throw new IllegalStateException("ApplicationEventMulticaster not initialized - " +
                "call 'refresh' before multicasting events via the context: " + this);
    }
    return this.applicationEventMulticaster;
}

通過分析,又回到了refresh這個核心函數中


image.png
public static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "applicationEventMulticaster";

// 初始化監聽器容器
protected void initApplicationEventMulticaster() {
    ConfigurableListableBeanFactory beanFactory = getBeanFactory();
    if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
           // 手動實現了名稱為applicationEventMulticaster的bean
        this.applicationEventMulticaster =
                beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
        // 還必須得是ApplicationEventMulticaster類
        if (logger.isDebugEnabled()) {
            logger.debug("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
        }
    }
    else {
           // 否則就默認自定義一個名稱為SimpleApplicationEventMulticaster的監聽器容器
        this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
        beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
        if (logger.isDebugEnabled()) {
            logger.debug("Unable to locate ApplicationEventMulticaster with name '" +
                    APPLICATION_EVENT_MULTICASTER_BEAN_NAME +
                    "': using default [" + this.applicationEventMulticaster + "]");
        }
    }
}

private final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<ApplicationListener<?>>();
// 默認是一個空列表,而不是null

// 注冊監聽器,并且加入到監聽器容器中
protected void registerListeners() {
    // Register statically specified listeners first.
    for (ApplicationListener<?> listener : getApplicationListeners()) {
        getApplicationEventMulticaster().addApplicationListener(listener);
        // 把提前存儲好的監聽器添加到監聽器容器中
    }

    String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
    // 獲取類型是ApplicationListener的beanName集合,此處不會去實例化bean
    for (String listenerBeanName : listenerBeanNames) {
        getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
    }
    
    Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
    this.earlyApplicationEvents = null;
    // 初始化事件為null
    if (earlyEventsToProcess != null) {
        for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
            getApplicationEventMulticaster().multicastEvent(earlyEvent);
        }
    }
}

這樣就很清楚了,在spring進行refresh的時候就完成了監聽器容器和監聽器的初始化工作(可以很方便的注冊自己需要的監聽器或者自定義的監聽器容器對象),只需要獲取到容器就可以直接publish事件了。

4、Spring Event

前面已經說了監聽器一般和具體的某一個事件綁定的(這點就和觀察者模式非常不一樣了),那么就來看看spring已經幫我們實現了哪些Event,具體如下圖

image.png
  • ApplicationContextEvent(Context...的抽象類)
  • ContextClosedEvent 生命周期關閉
  • ContextRefreshedEvent refresh完成
  • ContextStartedEvent 生命周期啟動
  • ContextStoppedEvent 生命周期停止
  • PayloadApplicationEvent
  • RequestHandledEvent
  • ServletRequestHandledEvent RequestHandledEvent的子類,Spring MVC 請求完成之后推送的事件

下面就ContextRefreshedEvent和ServletRequestHandledEvent具體分析下如何使用以及其實現的原理

4.1 ContextRefreshedEvent

image.png

在refresh結束之后推送的一個事件,這個時候大部分bean的實例化已經完成了,并且傳遞了this這個數據,那么如果有需要的話,可以在這個地方實現對Spring上下文數據的統計或者監控,簡直不能爽歪歪,但是個人開發目前還沒遇到具體的使用場景

4.2 ServletRequestHandledEvent

Spring MVC 中的事件,直接跳到DispatcherServlet類,后來到了FrameworkServlet 抽象類

image.png

如果可以推送事件的話,則利用web的上下文推送事件,其中包含了一個請求的基本信息,如果需要定制化統計整個HTTP請求的情況,完全可以通過這個事件推送實現

不過這里有一點需要注意到,還有個publishEvents字段,如果需要使用,需要設置為true,如下圖web.xml配置即可

image.png

5、異步Pushlish以及DEMO

異步推送采用的是多線程的方法,具體看SimpleApplicationEventMulticaster類,也就是上面說的監聽器容器

public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) {
    ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
    for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
           // 獲取符合事件類型的監聽器集合
        Executor executor = getTaskExecutor();
        if (executor != null) {
              // 如果線程池不為null,則提交一個新任務交由線程池運行
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    invokeListener(listener, event);
                }
            });
        }
        else {
              // 否則就是同步執行
            invokeListener(listener, event);
        }
    }
}

protected void invokeListener(ApplicationListener listener, ApplicationEvent event) {
    ErrorHandler errorHandler = getErrorHandler();
    if (errorHandler != null) {
          // 錯誤處理器不為null
        try {
            listener.onApplicationEvent(event);
        }
        catch (Throwable err) {
            errorHandler.handleError(err);
            // 當推送消息出現異常,利用錯誤處理器去處理該錯誤
        }
    }
    else {
        try {
            listener.onApplicationEvent(event);
        }
        catch (ClassCastException ex) {
            String msg = ex.getMessage();
            if (msg == null || msg.startsWith(event.getClass().getName())) {
                // Possibly a lambda-defined listener which we could not resolve the generic event type for
                Log logger = LogFactory.getLog(getClass());
                if (logger.isDebugEnabled()) {
                    logger.debug("Non-matching event type for listener: " + listener, ex);
                }
            }
            else {
                throw ex;
            }
        }
    }
}
  • 注入一個線程池可實現異步處理
  • 使用errorHandler可以做到最后的事件處理的兜底方案

那么問題來了,如何注入一個線程池呢?往哪里注入呢?

只需要手動實現applicationEventMulticaster的bean,并且利用Set注入的方法注入了一個線程池,線程池也需要實例化,就直接使用了spring自帶的簡單異步任務線程池

image.png

從同步改成異步,代碼不需要修改,直接注入這么一個bean即可(當然了XML配置和Config配置均可)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容