@[toc]

SpringBoot version 2.4.5 is used for all debugging.

ApplicationListener is an in-app event-driven mechanism provided by Spring. Namely Pub/Sub publish subscribe mechanism in the application of the internal implementation. It is generally used to monitor some health within the application, but can also be used in application development.

The specific implementation mechanism can be explored in Spring, here is a simple understanding of SpringBoot to this event-driven mechanism to do what encapsulation.

Event listener use

1. Implement your own event listener

First create a custom event listener:

public class MyApplicationListener implements ApplicationListener<ApplicationEvent> {
    @Override
    public void onApplicationEvent(ApplicationEvent applicationEvent) {
        System.out.println("======>MyApplicationListener: "+applicationEvent); }}Copy the code

Then, again, configure the listeners in your project’s spring.factories

org.springframework.context.ApplicationListener=\
com.roy.applicationListener.MyApplicationListener
Copy the code

Then configure the startup class. Publish an event of your own in the startup class.

@SpringBootApplication
public class P1Application implements CommandLineRunner {
    public static void main(String[] args) {
        final SpringApplication application = new SpringApplication(P1Application.class);
// application.addInitializers(new MyApplicationContextInitializer());
        application.run(args);
    }
    @Autowired
    private ApplicationContext applicationContext;

    @Override
    public void run(String... args) throws Exception {
        // Self-publish an event.
        applicationContext.publishEvent(new ApplicationEvent("selfEvent") {}); }}Copy the code

When the SpringBoot application is properly started, logs of critical events during the startup process are printed. Here are the key event logs:

======>MyApplicationListener: org.springframework.boot.context.event.ApplicationStartingEvent[source=org.springframework.boot.SpringApplication@60c6f5 b] ======>MyApplicationListener: org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent[source=org.springframework.boot.SpringApplica tion@60c6f5b] ======>MyApplicationListener: org.springframework.boot.context.event.ApplicationContextInitializedEvent[source=org.springframework.boot.SpringApplicat ion@60c6f5b] ======>MyApplicationListener: org.springframework.boot.context.event.ApplicationPreparedEvent[source=org.springframework.boot.SpringApplication@60c6f5 b] ======>MyApplicationListener: org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigAp plicationContext@1d296da, started on Wed Apr 28 13:33:33 CST 2021] ======>MyApplicationListener: org.springframework.boot.context.event.ApplicationStartedEvent[source=org.springframework.boot.SpringApplication@60c6f5b ] ======>MyApplicationListener: org.springframework.boot.availability.AvailabilityChangeEvent[source=org.springframework.context.annotation.AnnotationCo nfigApplicationContext@1d296da, started on Wed Apr 28 13:33:33 CST 2021] ======>MyApplicationListener: Com. Roy. P1Application $1 [source = selfEvent] # #!! Self-published events. ======>MyApplicationListener: org.springframework.boot.context.event.ApplicationReadyEvent[source=org.springframework.boot.SpringApplication@60c6f5b] ======>MyApplicationListener: org.springframework.boot.availability.AvailabilityChangeEvent[source=org.springframework.context.annotation.AnnotationCo nfigApplicationContext@1d296da, started on Wed Apr 28 13:33:33 CST 2021] ======>MyApplicationListener: org.springframework.context.event.ContextClosedEvent[source=org.springframework.context.annotation.AnnotationConfigAppli cationContext@1d296da, started on Wed Apr 28 13:33:33 CST 2021]Copy the code

This prints out several internal events during the SpringBoot application startup process. In fact, these internal events correspond to the various stages of the startup process and are a good way to sort through the SpringBoot startup process.

I won’t go into the details of the Spring event mechanism here, but you can find out for yourself. Let’s focus on SpringBoot again.

2. Other configuration methods of event listeners:

This event listening mechanism is a very important one in Spring and can be configured in a number of ways. In addition to the configuration based on the spring.factories file mentioned above, there are several other configurations.

2.1 SpringApplication.addListener

As with the Initializer, this event listener can be added directly to the SpringApplication.

@SpringBootApplication public class P1Application implements CommandLineRunner { public static void main(String[] args) { final SpringApplication application = new SpringApplication(P1Application.class); // Add event listeners. AddListeners (new MyApplicationListener()); application.run(args); }}Copy the code

2.2 Add based on annotations

Configure MyApplicationListener into Spring’s IOC container based on annotations.

@Configuration
public class MyApplicationListener implements ApplicationListener<ApplicationEvent> {
    @Override
    public void onApplicationEvent(ApplicationEvent applicationEvent) {
        System.out.println("======>MyApplicationListener: "+applicationEvent); }}Copy the code

2.3 Using the SpringBoot configuration file

Alternatively, you can configure it in the SpringBoot configuration file application.properties

context.listener.classes=com.roy.applicationListener.MyApplicationListener
Copy the code

Event listeners can be configured in either of these ways. In addition, actually can also be seen in the section ApplicationContextInitializer in the previous chapter, also extended in the application initialization within SpringBoot a lot through the expansion of the application to add event listener.

Ii. Interpretation of core mechanisms

There are many ways to use the event mechanism, and different configuration methods also have different loading processes. Again, we’ll just read how SpringBoot loads event listeners using the spring.factories file.

SpringBoot for listeners processing, also with ApplicationContextInitializer process is the same. First load all listeners in the SpringApplication constructor:

public SpringApplication(ResourceLoader resourceLoader, Class
       ... primarySources) {...this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
    	// Load all event listeners registered in spring.facotries
        this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class)); . }Copy the code

Then start all listeners in the SpringApplication’s run method:

public ConfigurableApplicationContext run(String... args) {... SpringApplicationRunListeners listeners =this.getRunListeners(args);/ / < = = = = SpringApplicationRunListener registration
        listeners.starting(bootstrapContext, this.mainApplicationClass); <=== Publishes ApplicationStartingEvent

        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);/ / < = = = in this process will release ApplicationEnvironmentPreparedEvent
            this.configureIgnoreBeanInfo(environment);
            Banner printedBanner = this.printBanner(environment);
            context = this.createApplicationContext();
            context.setApplicationStartup(this.applicationStartup);
            this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);/ / < = = release ApplicationContextInitializedEvent ApplicationPreparedEvent events
            this.refreshContext(context);// <=== Publishes the ContextRefreshedEvent event
            this.afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }

            listeners.started(context);<=== Release ApplicationStartedEvent AvailabilityChangeEvent event
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
            this.handleRunFailure(context, var10, listeners);
            throw new IllegalStateException(var10);
        }

        try {
            listeners.running(context);// Publish the ApplicationReadyEvent and AvailabilityChangeEvent events
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, var9, (SpringApplicationRunListeners)null);
            throw newIllegalStateException(var9); }}Copy the code

First, when you registered SpringApplicationRunListener will parse spring. Factories, read the org. Springframework. Boot. SpringApplicationRunListener configuration.

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
Copy the code

Then debugging, down from a publish event place can see SpringBoot event publishing EventPublishingRunListener at the core of the object. The initialMulticaster component is used to publish different events. He implements event listening by calling all registered event listeners in real time when the event is published.

public class EventPublishingRunListener implements SpringApplicationRunListener.Ordered {

	private final SpringApplication application;

	private final String[] args;

	private final SimpleApplicationEventMulticaster initialMulticaster;

	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); }}@Override
	public int getOrder(a) {
		return 0;
	}

	@Override
	public void starting(ConfigurableBootstrapContext bootstrapContext) {
		// Publish the event
        this.initialMulticaster
				.multicastEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args)); }... }Copy the code

Call SimpleApplicationEventMulticaster component release event in Spring.

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {...public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) { ResolvableType type = eventType ! =null ? eventType : this.resolveDefaultEventType(event);
        Executor executor = this.getTaskExecutor();
        // Look for registered listeners based on event
        Iterator var5 = this.getApplicationListeners(event, type).iterator();

        while(var5.hasNext()) { ApplicationListener<? > listener = (ApplicationListener)var5.next();if(executor ! =null) {
                executor.execute(() -> {
                    // Call the onApplicationEvent method of the Listener.
                    this.invokeListener(listener, event);
                });
            } else {
                this.invokeListener(listener, event); }}}Copy the code

Where the initialMulticaster object is already in the Spring-Context package. So this completes the sorting out of SpringBoot’s transaction listening mechanism.

This event mechanism is also a great extension point for using SpringBoot, because these events are already published by default during application startup, and you can overlay your desired application initialization. The order in which these critical events are published is also very important. For example, if your extension needs to use Spring’s IOC container, you can only listen for a few internal events after ContextRefreshedEvent.

Three, the core implementation of SpringBoot

Next, comb through the transaction listeners in SpringBoot that are registered by default with spring.factories

#spirng-boot.jar
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.env.EnvironmentPostProcessorApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
#spring-boot-autoconfigure
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
Copy the code

The next step is also to comb through a few representative transaction listener logic.

For example, BackgroundPreinitializer will load several time-consuming initialization tasks in advance during the application startup process and start a single thread to speed up the loading.

@Order(LoggingApplicationListener.DEFAULT_ORDER + 1)
public class BackgroundPreinitializer implements ApplicationListener<SpringApplicationEvent> {

    // This statement is very important
	/**
	 * System property that instructs Spring Boot how to run pre initialization. When the
	 * property is set to {@code true}, no pre-initialization happens and each item is
	 * initialized in the foreground as it needs to. When the property is {@code false}
	 * (default), pre initialization runs in a separate thread in the background.
	 * @since2.1.0 * /
	public static final String IGNORE_BACKGROUNDPREINITIALIZER_PROPERTY_NAME = "spring.backgroundpreinitializer.ignore";

	private static final AtomicBoolean preinitializationStarted = new AtomicBoolean();

	private static final CountDownLatch preinitializationComplete = new CountDownLatch(1);

	private static final boolean ENABLED;

	static{ ENABLED = ! Boolean.getBoolean(IGNORE_BACKGROUNDPREINITIALIZER_PROPERTY_NAME) && ! NativeDetector.inNativeImage() && Runtime.getRuntime().availableProcessors() >1;
	}
	// Listen for SpringApplicationEvent events. This event is the parent interface of several important events that were debugged earlier
	@Override
	public void onApplicationEvent(SpringApplicationEvent event) {
		if(! ENABLED) {return;
		}
		if (event instanceof ApplicationEnvironmentPreparedEvent
				&& preinitializationStarted.compareAndSet(false.true)) {
			performPreinitialization();
		}
		if ((event instanceof ApplicationReadyEvent || event instanceof ApplicationFailedEvent)
				&& preinitializationStarted.get()) {
			try {
				preinitializationComplete.await();
			}
			catch(InterruptedException ex) { Thread.currentThread().interrupt(); }}}... }Copy the code

The following is a brief summary of the other event listeners. Interested users are advised to debug the code themselves so that they can form their own understanding.

  • Org. Sf. The boot. ClearCachesApplicationListener: application context after the completion of the loading of cache cleanup, ContextRefreshedEvent respond to events

  • Org. Sf. The boot. Builder. ParentContextCloserApplicationListener: Monitored parents application context close events and to the children of their own application context spread, ParentContextAvailableEvent/ContextClosedEvent related events

  • Org. Sf. The boot. Context. FileEncodingApplicationListener: if the system file encoding and environment variables specified in different termination application startup. This is done by comparing the system attribute file.encoding with the environment variable spring.mandatory-file-encoding for equality (case insensitive).

  • Org. Sf. The boot. Context. Config. AnsiOutputApplicationListener: according to the spring. The output. The ANSI. AnsiOutput enabled parameter configuration

  • Org. Sf. The boot. Context. Config. DelegatingApplicationListener: forward after listening to the event to the environment variable context. The listener. Classes specify the event listener

  • Org. Sf. The boot. Context. Logging. LoggingSystem LoggingApplicationListener configuration. The configuration or default configuration specified using the logging.config environment variable

  • Org. Springframework. Boot. The env. EnvironmentPostProcessorApplicationListener: Load the EnvironmentPostProcessor configured in the spring.factories file.

  • Org. Sf. The boot. Liquibase. LiquibaseServiceLocatorApplicationListener using a can and Spring boot executable jar package replacement liquibase working version ServiceLocator

Org. Sf. The boot. Context. Config. ConfigFileApplicationListener: specify SpringBoot address configuration file. However, it was removed in version 2.4.5.

Finally, is there any think EnvironmentPostProcessorApplicationListener this event listeners are very interesting? SpringBoot directly hands over the loading of the EnvironmentPostProcessor mechanism in spring.factories to the event listener, so let’s take a look at the processing mechanism of EnvironmentPostProcessor while the iron is hot.

Four, EnvironmentPostProcessor use

EnvironmentPostProcessor does some additional processing after the environment information has been loaded. For example, the following example can be used to read another configuration file after SpringBoot has read application.properties:

//@Component
public class MyEnvironmentPostProcessor  implements EnvironmentPostProcessor{
 
	@Override
	public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
		try(InputStream input = new FileInputStream("E:\\ds.properties")) {
			Properties properties = new Properties();
			properties.load(input);
			PropertiesPropertySource propertySource = new PropertiesPropertySource("ve", properties);
			environment.getPropertySources().addLast(propertySource);
			System.out.println("==== Loading the external configuration file is complete ====");
		} catch(Exception e) { e.printStackTrace(); }}Copy the code

The same method can be configured in the spring.factories file, or added to the IOC container via the @Component annotation.

org.springframework.boot.env.EnvironmentPostProcessor=\
com.roy.environmentPostProcessor.MyEnvironmentPostProcessor
Copy the code

4. EnvironmentPostProcessor loading mechanism

EnvironmentPostProcessor by EnvironmentPostProcessorApplicationListener listening Spring event to replenish the subsequent processing of the operation environment. Let’s see what he’s been listening to.

public class EnvironmentPostProcessorApplicationListener implements SmartApplicationListener.Ordered {

    // The execution priority is very high
	/** * The default order for the processor. */
	public static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE + 10; .@Override
	public void onApplicationEvent(ApplicationEvent event) {
		if (event instanceof ApplicationEnvironmentPreparedEvent) {
			onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
		}
		if (event instanceof ApplicationPreparedEvent) {
			onApplicationPreparedEvent((ApplicationPreparedEvent) event);
		}
		if (event instanceofApplicationFailedEvent) { onApplicationFailedEvent((ApplicationFailedEvent) event); }}Copy the code

As you can see from these events, there is no IOC container reference in its child processor.

Then continue debugging down his onApplicationEnvironmentPreparedEvent method, to find his analytical spring. Factories, loading EnvironmentPostProcessor implementation mechanism. The mechanism is no longer to use SpringFactoriesLoader.

// How to handle EnvironmentPostProcessor.
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
		ConfigurableEnvironment environment = event.getEnvironment();
		SpringApplication application = event.getSpringApplication();
    	/ / key tracking getEnvironmentPostProcessors method
		for(EnvironmentPostProcessor postProcessor : getEnvironmentPostProcessors(event.getBootstrapContext())) { postProcessor.postProcessEnvironment(environment, application); }}Copy the code

All the way down the debugging, tracking to ReflectionEnvironmentPostProcessorsFactory getEnvironmentPostProcessors method.

public List<EnvironmentPostProcessor> getEnvironmentPostProcessors(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext) {
		Instantiator<EnvironmentPostProcessor> instantiator = new Instantiator<>(EnvironmentPostProcessor.class,
				(parameters) -> {
					parameters.add(DeferredLogFactory.class, logFactory);
					parameters.add(Log.class, logFactory::getLog);
					parameters.add(ConfigurableBootstrapContext.class, bootstrapContext);
					parameters.add(BootstrapContext.class, bootstrapContext);
					parameters.add(BootstrapRegistry.class, bootstrapContext);
				});
		return instantiator.instantiate(this.classNames);
	}
Copy the code

The parameters is to add some default parameter classes to the set of instances. Later subclasses can create constructors that contain these parameters for instantiation. Notice that this is a simple IOC attribute injection? Is there a more classic case of learning IOC?

Instantiator is the Instantiator class

private T instantiate(Class
        type) throws Exception { Constructor<? >[] constructors = type.getDeclaredConstructors(); Arrays.sort(constructors, CONSTRUCTOR_COMPARATOR);for(Constructor<? > constructor : constructors) {// Args comes from the classes specified above
			Object[] args = getArgs(constructor.getParameterTypes());
			if(args ! =null) {
				ReflectionUtils.makeAccessible(constructor);
				return(T) constructor.newInstance(args); }}throw new IllegalAccessException("Unable to find suitable constructor");
	}
Copy the code

5. EnvironmentPostProcessor implementation registered in SpringBoot

Again, let’s review the EnvironmentPostProcessor implementation that SpringBoot registered with spring.factories:

# spirng - the boot of the jar
# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor,\
org.springframework.boot.env.RandomValuePropertySourceEnvironmentPostProcessor,\
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\
org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor,\
org.springframework.boot.reactor.DebugAgentEnvironmentPostProcessor
Copy the code

These implementation classes are specific what, this I temporarily still do not see too understand, should be related to some specific details of the scene. The relevant information has not been found on the Internet. I’m not going to go into specifics.

To make fun of it, the Internet posts are always about how to read additional configuration files using EnvironmentPostProcesser, but none of these mechanisms have been analyzed.

Then a simple tracking the ConfigDataEnvironmentPostProcessor this implementation. Found that he was also introduced in the process of the implementation of the spring. The factories in the org. Springframework. Boot. The context. The config. ConfigDataLoader function mechanism. Another big, complicated functional mechanism, and I don’t quite understand what it does yet. I’ll make it up when I get to know it.