SpringBoot growth 3: SpringApplicationRunListeners of extension point

In the previous section, we were familiar with the context of spring application creation and run methods. In this section, we will first analyze the first interesting extension point in the context - spring application runlisteners.

As follows:

Position of SpringApplicationRunListeners in the run method

In the previous run methods, there are many container related methods and environment related methods, and the code of spring application runlisteners is relatively scattered.

We said that we should focus on the big and let go of the small. We analyze SpringApplicationRunListeners. You can summarize the run method code as follows:

public ConfigurableApplicationContext run(String... args) {
   StopWatch stopWatch = new StopWatch();
   stopWatch.start();
   ConfigurableApplicationContext context = null;
   Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
   configureHeadlessProperty();
   SpringApplicationRunListeners listeners = getRunListeners(args);
   listeners.starting();
   //Some logic
   return context;
}

As shown below:

The first two steps are unimportant and become as follows:

public ConfigurableApplicationContext run(String... args) {
   //Some logic
   SpringApplicationRunListeners listeners = getRunListeners(args);
   listeners.starting();
   //Some logic
   listeners.started(context);
    //Some logic
    listeners.running(context);
    //Some logic
   return context;
}

After knowing the location logic of spring applicationrunlisteners in the run method, we can basically see that it is a different method to obtain all listeners through one method and then execute listeners.

As shown in the figure below:

How is its extension designed and what are the highlights? Let's take a closer look.

How is the extension of spring application runlisteners designed?

First, the spring applicationrunlisteners extension is a list. How do listeners get it? The code is as follows:

    private SpringApplicationRunListeners getRunListeners(String[] args) {
        Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
        return new SpringApplicationRunListeners(logger,
                getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
    }
    SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners) {
        this.log = log;
        this.listeners = new ArrayList<>(listeners);
    }

You found that it was obtained according to the previous tool method, getSpringFactoriesInstances. This tool method can obtain the list of instance objects of all implementation classes of an interface at the specified location of classPath through classLoader.

What is obviously obtained here is the implementation list of the interface SpringApplicationRunListener. Then, an object SpringApplicationRunListeners is created to store the list, as shown in the following figure:

As shown in the figure above, ClassLoader will scan all resource files, including those in jars. Only the META-INF directory exists and the classes with definitions about SpringApplicationRunListeners in spring factories will be loaded into. For example, the jar of SpringBoot has the following definitions:

Although only one spring applicationrunlisteners implementation definition eventpublishingrunnistener is found in this example, this search idea and method is worthy of our reference.

Because ClassLoader is mainly responsible for loading resources and class files under ClassPath, encapsulating a tool can specify to query an interface implementation class.

In addition to storing the listeners List, the methods of SpringApplicationRunListeners are the same, as follows:

class SpringApplicationRunListeners {

    private final Log log;

    private final List<SpringApplicationRunListener> listeners;

    SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners) {
        this.log = log;
        this.listeners = new ArrayList<>(listeners);
    }

    void starting() {
        for (SpringApplicationRunListener listener : this.listeners) {
            listener.starting();
        }
    }

    void environmentPrepared(ConfigurableEnvironment environment) {
        for (SpringApplicationRunListener listener : this.listeners) {
            listener.environmentPrepared(environment);
        }
    }

    void contextPrepared(ConfigurableApplicationContext context) {
        for (SpringApplicationRunListener listener : this.listeners) {
            listener.contextPrepared(context);
        }
    }

    void contextLoaded(ConfigurableApplicationContext context) {
        for (SpringApplicationRunListener listener : this.listeners) {
            listener.contextLoaded(context);
        }
    }

    void started(ConfigurableApplicationContext context) {
        for (SpringApplicationRunListener listener : this.listeners) {
            listener.started(context);
        }
    }

    void running(ConfigurableApplicationContext context) {
        for (SpringApplicationRunListener listener : this.listeners) {
            listener.running(context);
        }
    }

    void failed(ConfigurableApplicationContext context, Throwable exception) {
        for (SpringApplicationRunListener listener : this.listeners) {
            callFailedListener(listener, context, exception);
        }
    }

Thinking point: the extension point SpringApplicationRunListeners, the core idea of the extension point, is to encapsulate a Listener interface implementation list into a class. This class traverses the interface implementation list through different methods, so as to realize expansion in different stages.

This idea is very worthy of our study.

Different extensions are made through the following methods

1) Extend the starting, started, failed, and running methods for the starting process

2) Extended environmentPrepared method for configuration file

3) Extend the contextPrepared and contextLoaded methods to the container

The whole above is shown in the figure below:

Many people told me that the code of SpringBoot is complex and difficult to understand. In fact, if you think of yourself as a SpringBoot developer, these are just the conventional thinking of development and design. If you disassemble the context and details step by step, there is nothing. When you have this mentality, you won't find it complex or difficult.

What are the extension designs for the default eventpublishing runlistener?

Since there is only one event publishing runlistener in the obtained spring application runlisteners, what is the extension design and implementation of this?

EventPublishing runlistener can be seen from its name. The design and implementation of EventPublishing is related to event publishing. Let's take a look:

//EventPublishingRunListener.java
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 void starting() {
        this.initialMulticaster.multicastEvent(
                new ApplicationStartingEvent(this.application, this.args));
    }

    //Omit other extension method logic
}

//SimpleApplicationEventMulticaster.java
    @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));
        for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
            Executor executor = getTaskExecutor();
            if (executor != null) {
                executor.execute(() -> invokeListener(listener, event));
            }
            else {
                invokeListener(listener, event);
            }
        }
    }

The above extension can be seen in general:

1) It encapsulates a component simpleapplicationeventmulticast, event broadcaster

2) The broadcaster executes the multicastEvent method to broadcast different events. In fact, broadcasting is to judge whether to execute events according to the type of events.

For example, ApplicationStartingEvent can be judged as follows:

if( Event instanceof ApplicationStartingEvent){
  //doSomething
}

3) Since it is broadcast, multiple people will be notified, so each event is actually broadcast to a List , each applicationlister will judge whether to execute this event and do some operations similar to step 2). In addition, this List The access method is also the list obtained from Classpath with the tool class, but it is just a filter based on the event type

The overall process is shown in the figure below:

It can be seen that there are many highlights in the design of event broadcasting: such as the design of query cache, ClassLoader search class, List collection encapsulation extension operation, and spring applicationrunlisteners unified classification management extension operation

In addition, here is a brief mention. There are mainly four applicationlisteners related to ApplicationStartingEvent. What they do is not very important. Just mention it here.

1) LoggingApplicationListener: initializes the logging system component. logback is used by default

2)BackgroundPreinitializer:

Start the background thread to initialize the converter, verifier, Json serializer, character set, etc

ConversionServiceInitializer

ValidationInitializer

MessageConverterInitializer

MBeanFactoryInitializer

JacksonInitializer

CharsetInitializer

3)DelegatingApplicationListener executes the listener entrusted in the context.listener.classes system property. It does not do anything by default

4) LiquibaseServiceLocatorApplicationListener replaces ServiceLocator with the executable version of Spring boot (I don't know what it means, it's not important)

Summary

In fact, in this section, we mainly learned about the design of Spring application runlisteners, an extension point of Spring boot. We didn't find the core functions of Spring container creation and web container startup, automatic assembly and configuration that we wanted to find.

However, you should learn a lot of design ideas and have a clearer understanding of the overall process of spring application. In the next section, we will continue to analyze the run method to see if we can find the logic we want.

See you next time!

This article is composed of blog one article multi posting platform OpenWrite release!

Tags: Java Spring Boot

Posted on Mon, 27 Sep 2021 19:04:22 -0400 by alex_lana