使用方法
spring監聽模式需要三個組件:
1. 事件,需要繼承ApplicationEvent,即觀察者模式中的"主題",可以看做一個普通的bean類,用於保存在事件監聽器的業務邏輯中需要的一些字段;
2. 事件監聽器,需要實現ApplicationListener<E extends ApplicationEvent>,即觀察者模式中的"觀察者",在主題發生變化時收到通知,並作出相應的更新,加泛型表示只監聽某種類型的事件;
3. 事件發布器,需要實現ApplicationEventPublisherAware,獲取spring底層組件ApplicationEventPublisher,並調用其方法發布事件,即"通知"觀察者。
其中,事件監聽器和事件發布器需要在springIOC容器中注冊。
示例Demo
事件類
import org.springframework.context.ApplicationEvent; /** * spring監聽機制中的"事件" * created on 2019-04-15 */ public class BusinessEvent extends ApplicationEvent { //事件的類型 private String type; /** * Create a new ApplicationEvent. * * @param source the object on which the event initially occurred (never {@code null}) * 即事件是在哪個對象上發生的 */ public BusinessEvent(Object source, String type) { super(source); this.type = type; } public String getType() { return type; } public void setType(String type) { this.type = type; } }
事件監聽器
import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Component; /** * spring監聽機制中"監聽器" * created on 2019-04-15 */ @Component public class BusinessListener implements ApplicationListener<BusinessEvent> { /** * 監聽到事件后做的處理 * @param event */ @Override public void onApplicationEvent(BusinessEvent event) { System.out.println("監聽到事件:" + event.getType()); } }
事件發布器
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.stereotype.Component; /** * spring事件監聽機制中的"事件發布器" * created on 2019-04-15 */ @Component public class BusinessPublisher implements ApplicationEventPublisherAware { //spring提供的事件發布組件 private ApplicationEventPublisher applicationEventPublisher; @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.applicationEventPublisher = applicationEventPublisher; } /** * 發布事件 */ public void publishEvent(BusinessEvent businessEvent) { System.out.println("發布事件:" + businessEvent.getType()); this.applicationEventPublisher.publishEvent(businessEvent); } }
容器配置類
/** * spring容器配置類 * 需要在容器中注冊事件監聽器、事件發布器 * created on 2019-04-15 */ @ComponentScan(basePackages = {"cn.monolog.bennett.observer.event.listener"}) public class BeanConfig { }
測試類
/** * 用於測試spring事件監聽 * created on 2019-04-15 */ public class Test { public static void main(String[] args) { //創建springIOC容器 ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig.class); //從容器中獲取事件發布器實例 BusinessPublisher businessPublisher = applicationContext.getBean(BusinessPublisher.class); //創建事件 BusinessEvent businessEvent = new BusinessEvent(new Test(), BusinessType.ALLOT.getName()); //發布事件 businessPublisher.publishEvent(businessEvent); } }
源碼分析
在觀察者模式中,主題發生改變時,會"通知"觀察者作出相應的操作,實現方式是獲取觀察者列表,然后遍歷、分別執行一遍其更新方法。那么,在spring事件監聽中,事件發生變化時,是如何"通知"到觀察者的呢?如上面的demo所述,我們是通過spring的組件ApplicationEventListener接口執行publishEvent方法發布事件的,而這個抽象方法在spring中只有一個實現,就是AbstractrApplicationContext,這是一個容器類。我們來跟進一下這個容器類對於發布事件的實現方法源碼:
protected void publishEvent(Object event, @Nullable ResolvableType eventType) { Assert.notNull(event, "Event must not be null"); // Decorate event as an ApplicationEvent if necessary ApplicationEvent applicationEvent; if (event instanceof ApplicationEvent) { applicationEvent = (ApplicationEvent) event; } else { applicationEvent = new PayloadApplicationEvent<>(this, event); if (eventType == null) { eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType(); } } // Multicast right now if possible - or lazily once the multicaster is initialized if (this.earlyApplicationEvents != null) { this.earlyApplicationEvents.add(applicationEvent); } else {
//獲取事件廣播器、然后廣播事件 getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType); } // Publish event via parent context as well... if (this.parent != null) { if (this.parent instanceof AbstractApplicationContext) { ((AbstractApplicationContext) this.parent).publishEvent(event, eventType); } else { this.parent.publishEvent(event); } } }
粗體部分語句:首先獲取事件廣播器、然后廣播事件。
所以問題分為兩部分:如何獲取事件廣播器、怎樣廣播事件。
1. 獲取事件廣播器
直接跟進上述語句——getApplicationEventMulticaster(),似乎找不到答案,因為這個方法是直接返回了AbstractApplicationContext類的屬性。問題轉化為:AbstractApplicationContext類中的事件廣播器屬性是什么時候被賦值的?這就要從容器創建說起了。springIOC容器創建有一個重要步驟——刷新容器refresh(),就是在AbstractApplicationContext中定義的,這個refresh()中包含了容器創建、初始化的諸多操作,其中兩個步驟與事件監聽有關,看一下源碼
public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. prepareRefresh(); // Tell the subclass to refresh the internal bean factory. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); // Initialize message source for this context. initMessageSource(); // Initialize event multicaster for this context. initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // Check for listener beans and register them. registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } // Destroy already created singletons to avoid dangling resources. destroyBeans(); // Reset 'active' flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; } finally { // Reset common introspection caches in Spring's core, since we // might not ever need metadata for singleton beans anymore... resetCommonCaches(); } } }
第一個步驟是initApplicationEventMulticaster,即初始化事件廣播器,繼續跟進源碼會發現,是先從BeanFactory中獲取,如果不存在,就新建一個。第二個步驟是registerListeners,即注冊監聽器,從容器中獲取所有ApplicationEventListener類型的組件,添加進事件廣播器。
2. 廣播事件
廣播事件的方法是寫在事件廣播器的實現類——SimpleApplicationEventMulticater中的。
@Override public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) { ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
//遍歷監聽器,分別執行invokeListener for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) { Executor executor = getTaskExecutor(); if (executor != null) { executor.execute(() -> invokeListener(listener, event)); } else { invokeListener(listener, event); } } }
從源碼中可以看出,SimpleApplicationEventMulticater從容器中獲取所有的監聽器列表,遍歷列表,對每個監聽器分別執行invokeListener方法,繼續跟進invokeListener方法,它會調用一個doInvokeListener,在這個doInvokeListner中:
@SuppressWarnings({"unchecked", "rawtypes"})
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
try {
//調用監聽器實現類的onApplicationEvent方法
listener.onApplicationEvent(event);
}
catch (ClassCastException ex) {
String msg = ex.getMessage();
if (msg == null || matchesClassCastMessage(msg, event.getClass())) {
// Possibly a lambda-defined listener which we could not resolve the generic event type for
// -> let's suppress the exception and just log a debug message.
Log logger = LogFactory.getLog(getClass());
if (logger.isDebugEnabled()) {
logger.debug("Non-matching event type for listener: " + listener, ex);
}
}
else {
throw ex;
}
}
}
終於看到我們熟悉的:onApplicationEvent方法,這就是暴露在外層、供我們使用的事件監聽方法;
也就是在這里,實現了觀察者模式中的——"通知"觀察者進行更新的操作。
