Spring Boot v2.4.4 source code analysis event mechanism part II

Spring Boot event publishing and listening mechanism

1, Event driven model

1. Practice of event oriented programming under Spring

The event driven model can minimize the coupling, and Spring has a perfect event publishing and processing mechanism. To complete a complete event oriented programming in Spring, you need the following three steps:

  • Customize an event that needs to inherit ApplicationEvent. Refer to Spring Boot v2.4.4 source code analysis (III) event mechanism I
  • The event publisher injects ApplicationEventPublisher object to publish events;
  • The event listener implements the ApplicationListener interface or uses the @ EventListener annotation (introduced in Spring 4.1);

Refer to the documentation for specific implementation details Better application events in Spring Framework 4.2.
During debugging, you will find that the injection event publisher ApplicationEventPublisher is actually the Spring Boot application context annotationconfigservletwebserver ApplicationContext.
See the UML class diagram of application context interface:

It can be seen that ApplicationContext inherits the interface ApplicationEventPublisher and provides event publishing function.

2. Expand the interview question: the difference between BeanFactory and ApplicationContext

A classic interview question can be derived here: the difference between BeanFactory and ApplicationContext in Spring.
From the above interface inheritance relationship, we can see that ApplicationContext inherits from the interface BeanFactory, so the functions provided by BeanFactory are supported by ApplicationContext, and ApplicationContext extends the following functions:

  • Extend the MessageResource interface to support message internationalization;
  • Extend the ApplicationEventPublisher interface to support event publishing;
  • Extend the ResourcePatternResolver interface to support resource loading;
In addition:
  • ApplicationContext provides automatic BeanPostProcessor registration function;
  • ApplicationContext provides automatic beanfactoryprocessor registration;
  • BeanFactroy injects beans in the form of delayed loading. On the contrary, ApplicationContext creates all beans at one time when the container is started;

Specific details can be referred to Chapter 3. The IoC container

3. The true face of the event publisher

Take a look at the annotation configservletwebserver ApplicationContext publishing event sequence diagram:

It can be seen that the annotation configservletwebserver ApplicationContext publishing event is ultimately delegated to simpleapplicationeventmulticast.

2, Event broadcaster

Take a look at the UML class diagram of simpleapplicationeventmulticast:

1. The top-level interface applicationeventmulticast specifies the event broadcaster function

In addition to Aware interface, the top-level interface of simpleapplicationeventmulticast is applicationeventmulticast. Applicationeventmulticast defines some methods to manage and broadcast events to applicationlister event listeners. Moreover, the official comments clearly point out that ApplicationEventPublisher (especially ApplicationContext) can delegate the actual event publishing function to the applicationeventmulticast implementation class.
The applicationeventmulticast interface specifies that the event broadcaster needs to have the following functions:

  • Register event listener function, which supports registration by instance and bean name;
    void addApplicationListener(ApplicationListener<?> listener);
    void addApplicationListenerBean(String listenerBeanName);
    
  • Remove the registered event listener function, which supports not only the removal by instance and bean name, but also the batch removal by Predicate assertion;
    void removeApplicationListener(ApplicationListener<?> listener);
    void removeApplicationListenerBean(String listenerBeanName);
    
    // Note that only event listeners registered through instances can be removed here, and event listeners registered through bean names cannot be removed
    void removeApplicationListeners(Predicate<ApplicationListener<?>> predicate);
    
    // Note that only event listeners registered by bean name can be removed here, and event listeners registered by instance cannot be removed
    void removeApplicationListenerBeans(Predicate<String> predicate);
    
  • Event broadcast function;
    void multicastEvent(ApplicationEvent event);
    void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType);
    

2. Abstractapplicationeventmulticast manages event listeners

The abstract class abstractapplicationeventmulticast implements the function of applicationeventmulticast to manage event listeners. It is not difficult to understand why abstractapplicationeventmulticast implements the BeanFactoryAware interface, because applicationeventmulticast supports registering and removing listeners by bean name, Therefore, beanFactory needs to obtain the event listener instance according to the bean name.

2.1 internal class and cache

Several internal classes are defined in abstractapplicationeventmulticast:

  • Defaultlistener retriever encapsulates the registered event listener. It operates on this class during registration and removal. The member variables applicationListeners and applicationListenerBeans represent the event listeners registered through the instance and bean names respectively;
    private class DefaultListenerRetriever {
    
    		public final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<>();
    		public final Set<String> applicationListenerBeans = new LinkedHashSet<>();
    		
    		// Get all event listeners
    		public Collection<ApplicationListener<?>> getApplicationListeners() {
    			List<ApplicationListener<?>> allListeners = new ArrayList<>(
    					this.applicationListeners.size() + this.applicationListenerBeans.size());
    			allListeners.addAll(this.applicationListeners);
    			if (!this.applicationListenerBeans.isEmpty()) {
    				BeanFactory beanFactory = getBeanFactory();
    				for (String listenerBeanName : this.applicationListenerBeans) {
    					try {
    						ApplicationListener<?> listener =
    								beanFactory.getBean(listenerBeanName, ApplicationListener.class); // Get from beanFactory
    						if (!allListeners.contains(listener)) { // duplicate removal
    							allListeners.add(listener);
    						}
    					}
    					catch (NoSuchBeanDefinitionException ex) {
    					}
    				}
    			}
    			AnnotationAwareOrderComparator.sort(allListeners);
    			return allListeners;
    		}
    	}
    
  • CachedListenerRetriever and defaultlistener retriever encapsulate all registered event listeners, but a listener can only listen to one event. When an event occurs, it is necessary to filter out listeners that can handle this event from defaultlistener retriever. In this way, when the same type of event occurs multiple times, it is bound to cause repeated screening; To avoid repeated filtering, abstractapplicationeventmulticast encapsulates the filtered event listeners into CachedListenerRetriever for caching. CachedListenerRetriever is similar to defaultlistener retriever, except that applicationListeners and applicationListenerBeans are modified by volatile to ensure thread visibility, and the listener needs to judge whether the cache has been loaded (whether applicationListeners or applicationListenerBeans are null) when calling getApplicationListeners() method to obtain events. The source code of CachedListenerRetriever is not pasted here.
  • ListenerCacheKey, event listener cache key, member variables eventType and sourceType represent event type and event source type respectively. It can be seen from here that the defaultlistener retriever caches the event listener according to the event type and event source type. The ListenerCacheKey also overrides the equals method: two listenercachekeys are considered to be the same only if the eventType and sourceType are the same.

The internal variables of abstractapplicationeventmulticast are as follows:

// Registered event listeners
private final DefaultListenerRetriever defaultRetriever = new DefaultListenerRetriever(); 
// Cache, the data source is defaultRetriever
final Map<ListenerCacheKey, CachedListenerRetriever> retrieverCache = new ConcurrentHashMap<>(64); 

private ClassLoader beanClassLoader;  // Injection through BeanClassLoaderAware interface
private ConfigurableBeanFactory beanFactory; // Injection through BeanFactoryAware interface

2.2 cache side cache mode

retrieverCache is of type ConcurrentHashMap and thread safe. However, defaultlistener Retriever is a non thread safe class. Considering thread safety, all read (thread visibility) and write operations to defaultRetriever must be locked.
Abstractapplicationeventmulticast cache uses the cache side mode. All modifications to the data source defaultRetriever will empty the cache.
For example:

// Omit the set/get methods of beanClassLoader and beanFactory

@Override
public void addApplicationListener(ApplicationListener<?> listener) {
	synchronized (this.defaultRetriever) {
		// Remove the agent to prevent the same event listener from being added more than once
		Object singletonTarget = AopProxyUtils.getSingletonTarget(listener);
		if (singletonTarget instanceof ApplicationListener) {
			this.defaultRetriever.applicationListeners.remove(singletonTarget);
		}
		this.defaultRetriever.applicationListeners.add(listener);
		this.retrieverCache.clear(); // wipe cache 
	}
}

// addApplicationListenerBean(String), removeApplicationListener(ApplicationListener<?>)
// removeApplicationListenerBean(String), removeApplicationListeners(Predicate<ApplicationListener<?>>)
// removeApplicationListenerBeans(Predicate<String> )
// The removeAllListeners() method is similar to the addapplicationlistener (applicationlistener <? >) method
// All operations are performed on the applicationListeners or applicationListenerBeans of the defaultRetriever first
// Then clear the retrieverCache cache cache, which is omitted here

// Get all event listeners
protected Collection<ApplicationListener<?>> getApplicationListeners() {
	synchronized (this.defaultRetriever) { // synchronized ensures that the changes made by other threads to the defaultRetriever are visible to the thread
		return this.defaultRetriever.getApplicationListeners();
	}
}

2.3 get listener of specified type

Getapplicationlisters (applicationevent, resolvable type) is an important method of abstractapplicationeventmulticast, which is used to obtain the event listener of the specified event type.

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); // Compute cache key

		// CachedListenerRetriever that needs to be filled with attribute values. If newRetriever is null, it means that attribute filling is not required
		CachedListenerRetriever newRetriever = null;

		// Quick check for hits in cache
		CachedListenerRetriever existingRetriever = this.retrieverCache.get(cacheKey);
		if (existingRetriever == null) {
			// Cache security?
			if (this.beanClassLoader == null ||
					(ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
							(sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
				newRetriever = new CachedListenerRetriever();
				// Note that the newRetriever put in at this time is the CachedListenerRetriever property
				// Empty shell child with no assignment (both applicationlisters and applicationListenerBeans are null)
				existingRetriever = this.retrieverCache.putIfAbsent(cacheKey, newRetriever);
				// There may be other threads between retrieverCache.get and retrievercache.putifabsent that put this type of event listener into the cache
				// Therefore, detection is also needed to avoid repeated attribute assignment
				if (existingRetriever != null) {
					newRetriever = null; 
				}
			}
		}

		if (existingRetriever != null) {
			Collection<ApplicationListener<?>> result = existingRetriever.getApplicationListeners();
			if (result != null) {
				return result;
			}
			// If the result is null, it means that there are other threads between retrieverCache.get and retrievercache.putifabsent to put this type of event listener into the cache
			// But it is still an empty shell of CachedListenerRetriever that did not have time to assign the attribute
			// You also need to call the function retrieveApplicationListeners to get
		}
		// Get event listeners with event type of eventType and event source type of sourceType from defaultRetriever
		// If the newRetriever is not null, you also need to assign the get event listener to the applicationListeners and applicationListenerBeans properties of the newRetriever
		return retrieveApplicationListeners(eventType, sourceType, newRetriever);
	}

If the cache misses, abstractapplicationeventmulticast will immediately create a new empty shell of CachedListenerRetriever without attribute assignment and put it into the cache, and then assign a value to the CachedListenerRetriever attribute put into the cache in the retrieveapplicationlisteners (resolvable type, class <? >, CachedListenerRetriever) function.
Consider the multi-threaded environment: if multiple threads miss the cache at the same time, there is no problem in putting the new CachedListenerRetriever into the cache (ConcurrentHashMap thread safety, putIfAbsent will only put the first call to CachedListenerRetriever into the cache), However, if each thread assigns the property of its newly created CachedListenerRetriever in the retrieveApplicationListeners function, isn't it necessary. Therefore, when the cache misses, put the newly created CachedListenerRetriever into the cache. If there is a return (put by other threads), you need to set the newRetriever to null, and then you don't need to assign a value to its property.

Take a look at the source code of the retrieveApplicationListeners function:

	private Collection<ApplicationListener<?>> retrieveApplicationListeners(
			ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable CachedListenerRetriever retriever) {

		List<ApplicationListener<?>> allListeners = new ArrayList<>(); // All filtered event listeners returned
		// Event listeners that have been filtered by eventType and sourceType to assign values to the retriever property
		Set<ApplicationListener<?>> filteredListeners = (retriever != null ? new LinkedHashSet<>() : null);
		Set<String> filteredListenerBeans = (retriever != null ? new LinkedHashSet<>() : null);

		Set<ApplicationListener<?>> listeners;
		Set<String> listenerBeans;
		synchronized (this.defaultRetriever) { // Use synchronized to ensure that other threads' modifications to the defaultRetriever are visible to this thread
			listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);
			listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);
		}

		// Filter event listeners that support eventType and sourceType from all event listeners
		for (ApplicationListener<?> listener : listeners) {
			if (supportsEvent(listener, eventType, sourceType)) {
				if (retriever != null) {
					filteredListeners.add(listener);
				}
				allListeners.add(listener);
			}
		}

		// Filter by bean name
		if (!listenerBeans.isEmpty()) {
			ConfigurableBeanFactory beanFactory = getBeanFactory();
			for (String listenerBeanName : listenerBeans) {
				try {
					// Initial filtering to avoid early instantiation during getBean
					if (supportsEvent(beanFactory, listenerBeanName, eventType)) {
						ApplicationListener<?> listener = // Get the instance from beanFactory according to the bean name
								beanFactory.getBean(listenerBeanName, ApplicationListener.class);
						// It may have been registered through the instance
						if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) {
							if (retriever != null) {
								if (beanFactory.isSingleton(listenerBeanName)) {
									filteredListeners.add(listener);
								}
								else {
									filteredListenerBeans.add(listenerBeanName);
								}
							}
							allListeners.add(listener);
						}
					}
					else {
						// Not supported. Remove event listeners registered by instance
						Object listener = beanFactory.getSingleton(listenerBeanName);
						if (retriever != null) {
							filteredListeners.remove(listener);
						}
						allListeners.remove(listener);
					}
				}
				catch (NoSuchBeanDefinitionException ex) { // The singleton bean was not found. It may be in the bean destruction stage
				}
			}
		}

		AnnotationAwareOrderComparator.sort(allListeners); // sort
		if (retriever != null) {
			// retriever attribute assignment
			if (filteredListenerBeans.isEmpty()) {
				retriever.applicationListeners = new LinkedHashSet<>(allListeners);
				retriever.applicationListenerBeans = filteredListenerBeans;
			}
			else {
				retriever.applicationListeners = filteredListeners;
				retriever.applicationListenerBeans = filteredListenerBeans;
			}
		}
		return allListeners;
	}

As can be seen from the above source code, the retrieveapplicationlisteners (resolvable type, eventtype, class <? > sourcetype, cachedlistenerretriever Retriever) function implements two functions:

  • Get the listener instance collection of the specified event type eventType and event source type sourceType (those registered by bean name need to be obtained through beanFactory);
  • If the retriever is not null, the instance collection and bean name collection obtained from the listener will be assigned to the retriever property;

2.4 screening listener

Filter specific event types and event source types from the defaultRetriever. The event listener is mainly implemented by the supportsEvent function. The supportsEvent function has two overloaded forms:

  • Supportsevent (configurablebeanfactory, beanfactory, string listenerbeanname, resolvable type, eventtype), Above It has been introduced that beanFactory injects beans in the form of delayed loading. The beans will be instantiated only when the getBean method is called for the first time. For beans that do not meet the conditions, calling getBean directly will cause unnecessary instantiation operations. This method avoids unnecessary instantiation operations by checking the bean definition event generics, but it can not be accurately judged.
    private boolean supportsEvent(
    		ConfigurableBeanFactory beanFactory, String listenerBeanName, ResolvableType eventType) {
    
    	Class<?> listenerType = beanFactory.getType(listenerBeanName);
    	// GenericApplicationListener inherits smartapplicationlister
    	// SmartApplicationListener inherits applicationlistener < applicationevent >
    	// listenerType is null, or smartapplicationlister is smartapplicationlister \ applicationlister < applicationevent > returns true
    	if (listenerType == null || GenericApplicationListener.class.isAssignableFrom(listenerType) ||
    			SmartApplicationListener.class.isAssignableFrom(listenerType)) {
    		return true;
    	}
    
    	// Judge whether the generic type inherits the eventType when the listener type implements the ApplicationListener interface
    	if (!supportsEvent(listenerType, eventType)) {
    		return false;
    	}
    	try {
    		BeanDefinition bd = beanFactory.getMergedBeanDefinition(listenerBeanName);
    		ResolvableType genericEventType = bd.getResolvableType().as(ApplicationListener.class).getGeneric();
    		// The ApplicationListener interface is not implemented and returns true
    		// Implements ApplicationListener and generic inheritance eventType also returns true
    		return (genericEventType == ResolvableType.NONE || genericEventType.isAssignableFrom(eventType));
    	}
    	catch (NoSuchBeanDefinitionException ex) { // Manually inject bean name
    		return true;
    	}
    }
    
  • Supportsevent (class <? > listenerType, resolvable type, eventType) to judge whether the generic type of the ApplicationListener interface inherits the eventType when the listenerType implements the ApplicationListener interface. However, if the listener type does not implement the ApplicationListener interface, the method will also return true;
    protected boolean supportsEvent(Class<?> listenerType, ResolvableType eventType) {
    	ResolvableType declaredEventType = GenericApplicationListenerAdapter.resolveDeclaredEventType(listenerType);
    	return (declaredEventType == null || declaredEventType.isAssignableFrom(eventType));
    }
    
  • Supportsevent (applicationlistener <? > listener, resolvable type, eventType, @ nullable class <? > sourceType), accurately judge whether the listener can listen to the event type eventType and the event source type sourceType;
    protected boolean supportsEvent(
    		ApplicationListener<?> listener, ResolvableType eventType, @Nullable Class<?> sourceType) {
    	GenericApplicationListener smartListener = (listener instanceof GenericApplicationListener ?
    			(GenericApplicationListener) listener : new GenericApplicationListenerAdapter(listener));
    	return (smartListener.supportsEventType(eventType) && smartListener.supportsSourceType(sourceType));
    }
    

3. Simpleapplicationeventmulticast event broadcast

Abstractapplicationeventmulticast implements the applicationeventmulticast interface management event listener function, so its subclass simpleapplicationeventmulticast only needs to implement the event broadcasting function of the applicationeventmulticast interface (defined by the multicastEvent method). Abstractapplicationeventmulticast defines the getapplicationlisters (applicationevent event, resolvable type, eventtype) method to get all event listeners of a specific event type , broadcasting events is also simple. You only need to call this method to obtain all event listeners, and then call the onApplicationEvent method of these event listeners to process event logic. Although the sales are small, you will face a risk: Rogue listeners may block the whole application.
To solve this problem, SimpleApplicationEventMulticaster defines an Executor type taskExecutor. If the Executor is not empty, the listener is executed in the Executor to realize asynchronous listening.
The source code of the core method of simpleapplicationeventmulticast broadcasting events is as follows:

@Override
public void multicastEvent(ApplicationEvent event) {
	multicastEvent(event, resolveDefaultEventType(event));
}

@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
	ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
	Executor executor = getTaskExecutor();
	// Loop call
	for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
		if (executor != null) {
			executor.execute(() -> invokeListener(listener, event));
		}
		else {
			invokeListener(listener, event);
		}
	}
}

private ResolvableType resolveDefaultEventType(ApplicationEvent event) {
	return ResolvableType.forInstance(event);
}

// Synchronous / asynchronous call listener
protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
	ErrorHandler errorHandler = getErrorHandler();
	if (errorHandler != null) {
		try {
			doInvokeListener(listener, event);
		}
		catch (Throwable err) { // Processing error
			errorHandler.handleError(err);
		}
	}
	else {
		doInvokeListener(listener, event);
	}
}

@SuppressWarnings({"rawtypes", "unchecked"})
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
	try {
		// Call listener listening logic
		listener.onApplicationEvent(event);
	}
	catch (ClassCastException ex) {
		String msg = ex.getMessage();
		if (msg == null || matchesClassCastMessage(msg, event.getClass()) ||
				(event instanceof PayloadApplicationEvent &&
						matchesClassCastMessage(msg, ((PayloadApplicationEvent) event).getPayload().getClass()))) {
			// Possibly a lambda-defined listener which we could not resolve the generic event type for
			// -> let's suppress the exception.
			Log loggerToUse = this.lazyLogger;
			if (loggerToUse == null) {
				loggerToUse = LogFactory.getLog(getClass());
				this.lazyLogger = loggerToUse;
			}
			if (loggerToUse.isTraceEnabled()) {
				loggerToUse.trace("Non-matching event type for listener: " + listener, ex);
			}
		}
		else {
			throw ex;
		}
	}
}

You can also use the @ EnableAsync + @Asnc method provided by Spring to realize asynchronous listening.

4. Event listener registration time

Here is another problem: when publishing events and calling getapplicationlisters (applicationevent, resolvable type, eventtype) to obtain event listeners, you should ensure that all listeners have been registered. When did these event listeners register?

  • EventPublishingRunListener (encapsulates the Spring Boot start time node into time and publishes it, see Spring Boot v2.4.4 source code analysis (III) event mechanism I )The internal simpleapplicationeventmulticast registers the event listener loaded through SPI mechanism when it is created;
    public EventPublishingRunListener(SpringApplication application, String[] args) {
    	this.application = application;
    	this.args = args;
    	this.initialMulticaster = new SimpleApplicationEventMulticaster();
    	for (ApplicationListener<?> listener : application.getListeners()) {
    		this.initialMulticaster.addApplicationListener(listener);
    	}
    }
    
  • Implement the applicationlister interface or the event listener annotated with @ EventListener. Spring Boot will call registerListeners() to register when refreshing the context.
    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 (!CollectionUtils.isEmpty(earlyEventsToProcess)) {
    		for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
    			getApplicationEventMulticaster().multicastEvent(earlyEvent);
    		}
    	}
    }
    
  • Manually call addApplicationListenerBean series methods of abstractapplicationeventmulticast to register;

Tags: Java Spring Spring Boot

Posted on Sun, 07 Nov 2021 21:49:20 -0500 by .-INSANE-.