1. Introduction to spring event monitoring
The Spring framework provides a set of event publishing and listening mechanism. For developers, they can publish events as long as they hold the ApplicationContext instance, and they can receive and process events as long as they implement the ApplicationListener interface. A typical use example is as follows:
-
Custom eventnotifyevent
public class NotifyEvent extends ApplicationEvent { public NotifyEvent(final String content) { super(content); } }
-
Custom event listener NotifyListener
@Component @Slf4j public class NotifyListener implements ApplicationListener<NotifyEvent> { @Override public void onApplicationEvent(NotifyEvent event) { log.info("onNotifyEvent:{}", event); } }
-
Custom event publisher EventPublisher
@Component public class EventPublisher implements ApplicationContextAware { private ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } public void publishNotify(NotifyEvent event) { applicationContext.publishEvent(event); } }
2. Source code analysis
In fact, readers should be clear that the implementation of any event release monitoring mechanism is the application of observer mode in the final analysis. In other words, the operation of this mechanism usually includes the following steps. The source code sequence diagram in Spring is as follows:
- Listener registers with publisher
- When an event occurs, the publisher filters the listeners registered locally according to the conditions, calls back their listening methods, and notifies the occurrence of the event
- The listener callback method is called to trigger processing logic and consumption events
2.1 listener registration
-
First, when the Spring framework is started, it will call the core container to initialize the AbstractApplicationContext#refresh() method. Readers who are not familiar with this triggering process can refer to it WebFlux service startup process . AbstractApplicationContext#refresh() method is very core. Each method called internally has its own function. This article mainly focuses on the following two methods:
- Abstractapplicationcontext#initapplicationeventmulticast() instantiates the broadcaster
- AbstractApplicationContext#registerListeners() register listener to broadcaster
@Override 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(); } } }
-
The logic of AbstractApplicationContext#initapplicationeventmulticast() is relatively simple, that is, instantiating the broadcaster object. You can know from the code that the broadcaster object instantiated here is simpleapplicationeventmulticast, and AbstractApplicationContext will hold the reference of this object
protected void initApplicationEventMulticaster() { ConfigurableListableBeanFactory beanFactory = getBeanFactory(); if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) { this.applicationEventMulticaster = beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class); if (logger.isTraceEnabled()) { logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]"); } } else { this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory); beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster); if (logger.isTraceEnabled()) { logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " + "[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]"); } } }
-
After the broadcaster instance is completed, register the listener, that is, the AbstractApplicationContext#registerListeners() method. You can see that the core logic of this method is to get the listener instance from the container and register it with the broadcaster created in the previous step. Although the logic is simple and clear, it is necessary to explain the operation of obtaining listener instances, because there are many sources of event listeners here:
- The listener instance obtained by the abstractapplicationcontext#getapplicationlisters() method is cached in the internal collection. It is easy to see that objects will be added to the collection when the abstractapplicationcontext#addapplicationlister() method is triggered. The call of this method mainly comes from two parts
- EventListenerMethodProcessor will annotate the @EventListener annotation method into ApplicationListener instances by reflection, and finally call AbstractApplicationContext#addApplicationListener() method to cache.
- ApplicationListenerDetector will collect the instances that implement the ApplicationListener interface hosted to Spring through the AbstractApplicationContext#addApplicationListener() method
- AbstractApplicationContext#getBeanNamesForType() method directly obtains the Bean instance name list of ApplicationListener from the container for registration. Here, it mainly deals with those factorybeans used to create ApplicationListener. This FactoryBean will not be cached by AbstractApplicationContext#addApplicationListener() method in the previous phase
protected void registerListeners() { // Register statically specified listeners first. for (ApplicationListener<?> listener : getApplicationListeners()) { getApplicationEventMulticaster().addApplicationListener(listener); } // Do not initialize FactoryBeans here: We need to leave all regular beans // uninitialized to let post-processors apply to them! String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false); for (String listenerBeanName : listenerBeanNames) { getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName); } // Publish early application events now that we finally have a multicaster... Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents; this.earlyApplicationEvents = null; if (earlyEventsToProcess != null) { for (ApplicationEvent earlyEvent : earlyEventsToProcess) { getApplicationEventMulticaster().multicastEvent(earlyEvent); } } }
- The listener instance obtained by the abstractapplicationcontext#getapplicationlisters() method is cached in the internal collection. It is easy to see that objects will be added to the collection when the abstractapplicationcontext#addapplicationlister() method is triggered. The call of this method mainly comes from two parts
-
The actual listener registration will be implemented by the abstractapplicationeventmulticast #addapplicationlistenerbean () method. You can see that the listener is stored and cached in the internal list of the broadcaster object without special operations
public void addApplicationListener(ApplicationListener<?> listener) { synchronized (this.retrievalMutex) { // Explicitly remove target for a proxy, if registered already, // in order to avoid double invocations of the same listener. Object singletonTarget = AopProxyUtils.getSingletonTarget(listener); if (singletonTarget instanceof ApplicationListener) { this.defaultRetriever.applicationListeners.remove(singletonTarget); } this.defaultRetriever.applicationListeners.add(listener); this.retrieverCache.clear(); } }
2.2 event release and consumption
-
The event publishing is completed by AbstractApplicationContext#publishEvent() method. You can see that the core logic is getapplicationeventmulticast(). Multicastevent (applicationevent, eventtype) publishes events using the broadcaster. Here, you will call the simpleapplicationeventmulticast #multicastevent() method
@Override public void publishEvent(ApplicationEvent event) { publishEvent(event, null); } 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); } } }
-
The processing flow of the simpleapplicationeventmulticast #multicastevent() method is as follows:
- Call the abstractapplicationeventmulticast #getapplicationlisters() method to match the published event type with the applicationlister registered in the broadcaster as a condition
- Call the SimpleApplicationEventMulticaster#invokeListener() method to input the listener and the published event into the parameters and call back the listener method
@Override public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) { ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event)); Executor executor = getTaskExecutor(); for (ApplicationListener<?> listener : getApplicationListeners(event, type)) { if (executor != null) { executor.execute(() -> invokeListener(listener, event)); } else { invokeListener(listener, event); } } }
-
The main process of abstractapplicationeventmulticast #getapplicationlisters() method is to first take out the matched listeners from the cache. If the current event type has not been cached, call abstractapplicationeventmulticast # retrieveapplicationlisteners() method to retrieve and find all listeners consuming the current event type from the local listener list, Cache it when it is found
protected Collection<ApplicationListener<?>> getApplicationListeners( ApplicationEvent event, ResolvableType eventType) { Object source = event.getSource(); Class<?> sourceType = (source != null ? source.getClass() : null); ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType); // Quick check for existing entry on ConcurrentHashMap... ListenerRetriever retriever = this.retrieverCache.get(cacheKey); if (retriever != null) { return retriever.getApplicationListeners(); } if (this.beanClassLoader == null || (ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) && (sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) { // Fully synchronized building and caching of a ListenerRetriever synchronized (this.retrievalMutex) { retriever = this.retrieverCache.get(cacheKey); if (retriever != null) { return retriever.getApplicationListeners(); } retriever = new ListenerRetriever(true); Collection<ApplicationListener<?>> listeners = retrieveApplicationListeners(eventType, sourceType, retriever); this.retrieverCache.put(cacheKey, retriever); return listeners; } } else { // No ListenerRetriever caching -> no synchronization necessary return retrieveApplicationListeners(eventType, sourceType, null); } }
-
The core logic for retrieving matching events and listeners in the abstractapplicationeventmulticast #retrieveapplicationlisteners() method is the abstractapplicationeventmulticast #supportsevent() method
private Collection<ApplicationListener<?>> retrieveApplicationListeners( ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable ListenerRetriever retriever) { List<ApplicationListener<?>> allListeners = new ArrayList<>(); Set<ApplicationListener<?>> listeners; Set<String> listenerBeans; synchronized (this.retrievalMutex) { listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners); listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans); } // Add programmatically registered listeners, including ones coming // from ApplicationListenerDetector (singleton beans and inner beans). for (ApplicationListener<?> listener : listeners) { if (supportsEvent(listener, eventType, sourceType)) { if (retriever != null) { retriever.applicationListeners.add(listener); } allListeners.add(listener); } } // Add listeners by bean name, potentially overlapping with programmatically // registered listeners above - but here potentially with additional metadata. if (!listenerBeans.isEmpty()) { ConfigurableBeanFactory beanFactory = getBeanFactory(); for (String listenerBeanName : listenerBeans) { try { if (supportsEvent(beanFactory, listenerBeanName, eventType)) { ApplicationListener<?> listener = beanFactory.getBean(listenerBeanName, ApplicationListener.class); if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) { if (retriever != null) { if (beanFactory.isSingleton(listenerBeanName)) { retriever.applicationListeners.add(listener); } else { retriever.applicationListenerBeans.add(listenerBeanName); } } allListeners.add(listener); } } else { // Remove non-matching listeners that originally came from // ApplicationListenerDetector, possibly ruled out by additional // BeanDefinition metadata (e.g. factory method generics) above. Object listener = beanFactory.getSingleton(listenerBeanName); if (retriever != null) { retriever.applicationListeners.remove(listener); } allListeners.remove(listener); } } catch (NoSuchBeanDefinitionException ex) { // Singleton listener instance (without backing bean definition) disappeared - // probably in the middle of the destruction phase } } } AnnotationAwareOrderComparator.sort(allListeners); if (retriever != null && retriever.applicationListenerBeans.isEmpty()) { retriever.applicationListeners.clear(); retriever.applicationListeners.addAll(allListeners); } return allListeners; }
-
After finding the listener that consumes the current event type, go back to the implementation of the simpleapplicationeventmulticast #invokelistener() method in step 2 of this section. You can see that the Application#onApplicationEvent() method will be called back to notify the consumer of the event until the event is published and the consumption process ends
protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) { ErrorHandler errorHandler = getErrorHandler(); if (errorHandler != null) { try { doInvokeListener(listener, event); } catch (Throwable err) { errorHandler.handleError(err); } } else { doInvokeListener(listener, event); } } private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) { try { 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.isTraceEnabled()) { logger.trace("Non-matching event type for listener: " + listener, ex); } } else { throw ex; } } }