探析 Spring 容器內部事件釋出

發表於2023-09-28

其實在 JDK 中已經提供相應的自定義事件釋出功能的基礎類:

  • java.util.EventObject類 :自定義事件型別
  • java.util.EventListener介面:事件的監聽器

首先了解幾個概念:
image.png

Spring 事件類結構

image.png

1. 事件類

事件類也就是定義傳送的內容,比如可以透過繼承ApplicationContextEvent來自定義一個特定事件類。
image.png

1.1 ApplicationEvent

首先是繼承 EventObjectApplicationEvent,透過source來指定事件源:

public abstract class ApplicationEvent extends EventObject {
    /**
     * Constructs a prototypical Event.
     *
     * @param source The object on which the Event initially occurred.
     * @throws IllegalArgumentException if source is null.
     */
    public ApplicationEvent(Object source) {
        super(source);
    }
}

1.2 ApplicationContextEvent

是主要的容器事件,它有容器啟動、重新整理、停止以及關閉各種事件的子類。

public class ApplicationContextEvent extends ApplicationEvent {

    /**
     * Constructs a prototypical Event.
     *
     * @param source The object on which the Event initially occurred.
     * @throws IllegalArgumentException if source is null.
     */
    public ApplicationContextEvent(Object source) {
        super(source);
    }

    /**
     * Get the <code>ApplicationContext</code> that the event was raised for.
     */
    public final ApplicationContext getApplicationContext() {
        return (ApplicationContext) getSource();
    }

}

public class ContextClosedEvent extends ApplicationContextEvent{

    /**
     * Constructs a prototypical Event.
     *
     * @param source The object on which the Event initially occurred.
     * @throws IllegalArgumentException if source is null.
     */
    public ContextClosedEvent(Object source) {
        super(source);
    }

}

public class ContextRefreshedEvent extends ApplicationContextEvent{
    /**
     * Constructs a prototypical Event.
     *
     * @param source The object on which the Event initially occurred.
     * @throws IllegalArgumentException if source is null.
     */
    public ContextRefreshedEvent(Object source) {
        super(source);
    }

}

我們可以透過繼承該類來實現,特定的事件型別需求,比如要實現一個郵件傳送事件。只需要繼承ApplicationContextEvent即可:

public class MailSendEvent extends ApplicationContextEvent {
    private String msg;

    public MailSendEvent(Object source, String msg) {
        super(source);
        this.msg = msg;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

同時ApplicationContextEvent也有特定的幾個子類,來表示容器啟動、重新整理、停止以及關閉事件:
image.png

2.事件監聽器

事件監聽器介面中,只定義了一個方法:onApplicationEvent(E event)該方法接收ApplicationEvent事件物件,在該方法中編寫事件的響應處理邏輯。

public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

    /**
     * 接收ApplicationEvent 事件物件
     * 在該方法中編寫事件的響應處理邏輯
     * @param event
     */
    void onApplicationEvent(E event);
}

我們同樣也可以實現該介面來實現特定的事件監聽器功能,比如郵件傳送的監聽器:

public class MailSenderListener implements ApplicationListener<MailSendEvent> {

    @Override
    public void onApplicationEvent(MailSendEvent event) {
        System.out.println("郵件傳送器的 resource:" + event.getSource() + "郵件傳送器的 msg:" + event.getMsg());
    }
}

3.事件廣播器

事件廣播器負責將事件通知監聽器登錄檔中的事件監聽器,然後再由事件監聽器分別對事件進行響應。Spring中定義瞭如下介面:
image.png

public interface ApplicationEventMulticaster {

    /**
     * 新增事件監聽器
     * @param listener
     */
    void addApplicationListener(ApplicationListener<?> listener);

    /**
     * 移除事件監聽器
     * @param listener
     */
    void removeApplicationListener(ApplicationListener<?> listener);

    /**
     * 廣播事件
     * @param event
     */
    void multicastEvent(ApplicationEvent event);
}

及其簡單實現類SimpleApplicationEventMulticaster

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster{

    public SimpleApplicationEventMulticaster(BeanFactory beanFactory) {
        setBeanFactory(beanFactory);
    }
    /**unchecked 表示告訴編譯器忽略指定的警告,不用再編譯完成後出現警告資訊*/
    @SuppressWarnings("unchecked")
    @Override
    public void multicastEvent(ApplicationEvent event) {
        for (ApplicationListener applicationListener : getApplicationListeners(event)) {
            applicationListener.onApplicationEvent(event);
        }
    }
}

4.事件釋出者

它本身作為事件源,會在合適的時點,將相應事件釋出給對應的事件監聽器:

public interface ApplicationEventPublisher {

    /**
     * 通知監聽者併發布事件
     * @param event
     */
    void publishEvent(ApplicationEvent event);
}

在Spring容器事件中,ApplicationContext介面定義繼承了ApplicationEventPublisher介面,所以實際上AbstractApplicationContext在事件中承擔了事件釋出者的角色。
但是在實際上具體實現事件的釋出和事件監聽器註冊方面,將功能轉接給ApplicationEventMulticaster介面,最終具體實現則放在AbstractApplicationEventMulticaster的實現類中:
image.png

Spring 事件類的應用

那麼在Spring中,事件類到底是如何執行的呢?首先我們會在xml配置檔案中配置相應的ApplicationListener型別的監聽器,因此在容器啟動後,這些型別的bean會被ApplicationContext容器所識別,它們負責監聽容器內釋出的對應的ApplicationEvent型別的事件。

<bean class="cn.ethan.springframework.test.event.ContextRefreshedEventListener"/>
<bean class="cn.ethan.springframework.test.event.MailSenderListener"/>
<bean class="cn.ethan.springframework.test.event.ContextClosedEventListener"/>

AbstractApplicationContextrefresh()方法中可以看到自動註冊的內容:

public void refresh() throws BeansException {

        // 6. 初始化事件釋出者
        initApplicationEventMulticaster();

        // 7. 註冊事件監聽器
        registerListeners();

        // 9. 釋出容器重新整理完成事件
        finishRefresh();
}

private void initApplicationEventMulticaster() {
    ConfigurableListableBeanFactory beanFactory = getBeanFactory();
    applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
    beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, applicationEventMulticaster);
}

private void registerListeners() {
    Collection<ApplicationListener> applicationListeners = getBeansOfType(ApplicationListener.class).values();
    for (ApplicationListener listener : applicationListeners) {
        applicationEventMulticaster.addApplicationListener(listener);
    }
}

private void finishRefresh() {
    publishEvent(new ContextRefreshedEvent(this));
}
public void publishEvent(ApplicationEvent event) {
    applicationEventMulticaster.multicastEvent(event);
}

所以在ApplicationContext容器啟動時,會自動註冊EventListener型別的 Bean,一旦檢測到有ApplicationContextEvent型別的事件釋出,將通知這些註冊到容器的EventListener

應用例項

下面將構建一個傳送郵件的Spring事件例項:

1. 郵件傳送事件MailSendEvent

public class MailSendEvent extends ApplicationContextEvent {
    private String msg;

    public MailSendEvent(Object source, String msg) {
        super(source);
        this.msg = msg;
    }

    public String getMsg() {
        return msg;
    }
}

2.郵件傳送事件監聽器MailSendListener(郵件傳送事件)、ContextRefreshedEventListener(容器重新整理事件) 和 ContextClosedEventListener(容器關閉事件)

public class MailSenderListener implements ApplicationListener<MailSendEvent> {

    @Override
    public void onApplicationEvent(MailSendEvent event) {
        System.out.println("郵件傳送器的 resource:" + event.getSource() + "郵件傳送器的 msg:" + event.getMsg());
    }
}
public class ContextClosedEventListener implements ApplicationListener<ContextClosedEvent> {

    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        System.out.println("關閉事件:" + this.getClass().getName());
    }
}
public class ContextRefreshedEventListener implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        System.out.println("重新整理/開啟事件:" + this.getClass().getName());
    }
}

這時,將監聽器們注入xml檔案中:

<bean class="cn.ethan.springframework.test.event.ContextRefreshedEventListener"/>
<bean class="cn.ethan.springframework.test.event.MailSenderListener"/>
<bean class="cn.ethan.springframework.test.event.ContextClosedEventListener"/>

3.郵件傳送事件釋出者

事件釋出者ApplicationEventPublisher,因為前面提到,applicationContext繼承了ApplicationEventPublisher,而applicationContext將事件釋出功能委託給了ApplicationEventMulticaster,容器在啟動開始就會檢查是否存在名稱為applicationEventMulticasterApplicationEventMulticaster物件例項,如果有就使用提供的實現,沒有則預設初始化一個SimpleApplicationEventMulticaster作為將會使用的ApplicationEventMulticaster

/**
 * @description: 實現了事件監聽器的管理功能
 * @author: wjw
 * @date: 2022/7/9
 */
public abstract class AbstractApplicationEventMulticaster implements ApplicationEventMulticaster, BeanFactoryAware  {

    public final Set<ApplicationListener<ApplicationEvent>> applicationListeners = new LinkedHashSet<>();

    private BeanFactory beanFactory;

    @Override
    public void addApplicationListener(ApplicationListener<?> listener) {
        applicationListeners.add((ApplicationListener<ApplicationEvent>) listener);
    }

    @Override
    public void removeApplicationListener(ApplicationListener<?> listener) {
        applicationListeners.remove(listener);
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }

    /**
     * 獲得監聽器
     * @param event
     * @return
     */
    protected Collection<ApplicationListener> getApplicationListeners(ApplicationEvent event) {
        LinkedList<ApplicationListener> allListeners = new LinkedList<>();
        for (ApplicationListener<ApplicationEvent> listener : allListeners) {
            if (supportsEvent(listener, event)) {
                allListeners.add(listener);
            }
        }
        return allListeners;
    }

    protected boolean supportsEvent(ApplicationListener<ApplicationEvent> applicationListener, ApplicationEvent event) {
        Class<? extends ApplicationListener> listenerClass = applicationListener.getClass();

        /**根據不同例項化型別,判斷後獲取對應目標 class*/
        Class<?> targetClass = ClassUtils.isCglibProxyClass(listenerClass) ? listenerClass.getSuperclass() : listenerClass;
        Type genericInterface = targetClass.getGenericInterfaces()[0];

        Type actualTypeArgument = ((ParameterizedType) genericInterface).getActualTypeArguments()[0];
        String className = actualTypeArgument.getTypeName();
        Class<?> eventClassName;
        try {
            eventClassName = Class.forName(className);
        } catch (ClassNotFoundException e) {
            throw new BeansException("wrong event class name: " + className);
        }

        return eventClassName.isAssignableFrom(event.getClass());
    }

}
public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster{

    public SimpleApplicationEventMulticaster(BeanFactory beanFactory) {
        setBeanFactory(beanFactory);
    }
    /**unchecked 表示告訴編譯器忽略指定的警告,不用再編譯完成後出現警告資訊*/
    @SuppressWarnings("unchecked")
    @Override
    public void multicastEvent(ApplicationEvent event) {
        for (ApplicationListener applicationListener : getApplicationListeners(event)) {
            applicationListener.onApplicationEvent(event);
        }
    }
}

4.測試驗證

public void test_event() {
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring.xml");

    applicationContext.publishEvent(new CustomEvent(applicationContext, 110L, "test!"));

    System.out.println("-----------------------------------------------------------------");
    applicationContext.publishEvent(new MailSendEvent(applicationContext, "郵件傳送測試"));
    applicationContext.registerShutdownHook();
}
重新整理/開啟事件:cn.ethan.springframework.test.event.ContextRefreshedEventListener$$EnhancerByCGLIB$$2e5c458
-----------------------------------------------------------------
郵件傳送器的 resource:cn.ethan.springframework.context.support.ClassPathXmlApplicationContext@5f2050f6郵件傳送器的 msg:郵件傳送測試
關閉事件:cn.ethan.springframework.test.event.ContextClosedEventListener$$EnhancerByCGLIB$$fbc2c978

相關文章