In-depth study of SpringApplication in Spring Boot

In the entry class of Spring Boot, we usually SpringApplicationstart the Spring Boot project by calling the run method. In this section, we will in-depth study some details of SpringApplication.

Custom SpringApplication

By default, we SpringApplicationstart Spring Boot directly through the run method. In fact, we can adjust certain behaviors through some APIs.

Adjust via SpringApplication API

We create a new SpringBoot project, the Spring Boot version is 2.1.0.RELEASE, artifactIdis SpringApplication, and introduces spring-boot-starter-webdependencies. The project structure is as follows:

We change the code of the entry class to:

SpringApplication application = new SpringApplication(DemoApplication.class);
application.setBannerMode(Banner.Mode.OFF);
application.setWebApplicationType(WebApplicationType.NONE);
application.setAdditionalProfiles("dev");
application.run(args);

By calling SpringApplicationthe method, we turned off the printing of Banner, set the application environment as a non-WEB application, and specified the profiles as dev. In addition, SpringApplicationit also contains many other methods, you can check the source code or official documents for details:

Adjust via SpringApplicationBuilder API

SpringApplicationBuilderProvides Fluent API, which can realize chain call. The following code is consistent with the above effect, but it is more convenient to write:

new SpringApplicationBuilder(DemoApplication.class)
        .bannerMode(Banner.Mode.OFF)
        .web(WebApplicationType.NONE)
        .profiles("dev")
        .run(args);

SpringApplication preparation phase

SpringApplicationThe life cycle phases of the system can be roughly divided into preparation phase and operation phase.

The SpringApplicationparameterized constructor that we view through the source code :

Through the code in the parameterized constructor, we can divide SpringApplicationthe preparation phase into the following steps:

Configuration source

this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));This line of code in the constructor is used to load the Spring Boot Bean source we configured. Usually we use SpringApplicationor SpringApplicationBuilderconstructor directly specify the source.

The so-called Spring Boot Bean source refers to an @SpringBootApplicationannotated class, such as the entry class:

We can also change the above code to the following way:

public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(ApplicationResource.class);
        application.run(args);
    }

    @SpringBootApplication
    public static class ApplicationResource {

    }
}

This is also feasible. Viewed SpringApplicationsingle parameter constructor:

Explain that in addition to configuring a single source, we can also configure multiple sources.

Infer application type

This line of this.webApplicationType = WebApplicationType.deduceFromClasspath();code in the constructor is used to infer the current Spring Boot application type.

After Spring Boot 2.0, applications can be divided into the following three types:

  1. WebApplicationType.NONE: Non-WEB type;

  2. WebApplicationType.REACTIVE: Web Reactive type;

  3. WebApplicationType.SERVLET: Web Servlet type.

WebApplicationType.deduceFromClasspath()Or according to whether there are related implementation classes in the current application ClassPath to determine which application type is, deduceFromClasspaththe source code of the method is as follows:

We can also directly by SpringApplicationthe setWebApplicationTypemethod or SpringApplicationBuilderthe webmethod used to specify the type of the current application.

Load the application context initiator

Then the next line of code is setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));used to load the application context initiator ApplicationContextInitializer.

getSpringFactoriesInstancesThe source code of the method is as follows:

The above code uses the Spring factory loading mechanism to instantiate ApplicationContextInitializerand sort implementation classes.

So we can ApplicationContextInitializerimplement some custom operations before the Spring Boot application is initialized by implementing the interface.

For example, in com.example.demothe new initializerpackage, and then create a HelloApplicationContextInitializerclass that implements ApplicationContextInitializerthe interface:

@Order(Ordered.HIGHEST_PRECEDENCE)
public class HelloApplicationContextInitializer implements ApplicationContextInitializer {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("ConfigurableApplicationContext.id - " + applicationContext.getId());
    }
}

The initialize method is implemented in the above code, and the @Orderpriority is specified using annotations. Where Ordered.HIGHEST_PRECEDENCEis equal to Integer.MIN_VALUE, Ordered.LOWEST_PRECEDENCEequal to Integer.MAX_VALUE. So the smaller the value, the higher the priority.

In addition to using @Orderannotations to specify the priority, we can also specify the priority by implementing the org.springframework.core.Orderedinterface getOrdermethod.

Next, let's create an HelloApplicationContextInitializerInitializer with a lower priority ratio --  AfterHelloApplicationContextInitializer:

public class AfterHelloApplicationContextInitializer implements ApplicationContextInitializer, Ordered {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("AfterHelloApplicationContextInitializer: " + applicationContext.getId());
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }
}

The above getOrdermethod specifies the priority as the lowest priority.

After creation, we also need to configure these two implementation classes in the factory configuration file. Create a new META-INF directory under the resources directory and create a spring.factories file:

# Initializers
org.springframework.context.ApplicationContextInitializer=\
com.example.demo.initializer.HelloApplicationContextInitializer,\
com.example.demo.initializer.AfterHelloApplicationContextInitializer

At this time, when you start the Spring Boot project, you will find that the console executes these two initializers after printing the Banner, and HelloApplicationContextInitializerthe initializemethod execution time is earlier than AfterHelloApplicationContextInitializerthe initializemethod:

Load application event listener

After loading the application context initiator, the next line of setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));code loads the application event listener. Similar to loading the event context initiator, Spring Boot is also ApplicationListeneran implementation class that is instantiated through Spring's factory method and sorted.

Since it is an event monitor, what events can it monitor? It listens to ApplicationEventthe implementation class of the interface. Let's check which events implement this interface:

Here we take ContextClosedEventas an example to write the application event listeners custom Spring context monitor off event.

In com.example.democase the new listenerpackage, and then create a ContextClosedEventListenerclass that implements ApplicationListenerthe interface:

@Order(Ordered.HIGHEST_PRECEDENCE)
public class ContextClosedEventListener implements ApplicationListener<ContextClosedEvent> {

    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        System.out.println("ContextClosedEvent: " + event.getApplicationContext().getId());
    }
}

The above code implements the ContextClosedEventmonitoring of events and assigns the highest priority.

Then create a lower priority than ContextClosedEventListenerthe above code to implement the ContextClosedEventevent listener AfterContextClosedEventListener:

public class AfterContextClosedEventListener implements ApplicationListener<ContextClosedEvent>, Ordered {
    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        System.out.println("AfterContextClosedEventr: " + event.getApplicationContext().getId());
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE + 1;
    }
}

Finally, don't forget to configure in the Spring factory configuration file:

# Application Listeners
org.springframework.context.ApplicationListener=\
com.example.demo.listener.ContextClosedEventListener,\
com.example.demo.listener.AfterContextClosedEventListener

Specify the environment as a non-WEB environment in the Spring Boot entry class (so that the application will be closed immediately after startup):

new SpringApplicationBuilder(DemoApplication.class)
                .web(WebApplicationType.NONE)
                .run(args);

Run the Spring Boot entry class, the console output is as follows:

Infer the entry class

Then the next line of code in the constructor is this.mainApplicationClass = deduceMainApplicationClass();used to infer the entry class for running the Spring Boot application. View deduceMainApplicationClassmethod source code:

The main logic of the code is to determine the actual entry class based on the execution stack of the Main thread.

After the introduction of the preparation phase is completed, the next step is to introduce the operation phase.

SpringApplication runtime phase

SpringApplication operational phase corresponds to SpringApplicationthe runway we view its source code:

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();
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
                applicationArguments);
        configureIgnoreBeanInfo(environment);
        Banner printedBanner = printBanner(environment);
        context = createApplicationContext();
        exceptionReporters = getSpringFactoriesInstances(
                SpringBootExceptionReporter.class,
                new Class[]{ConfigurableApplicationContext.class}, context);
        prepareContext(context, environment, listeners, applicationArguments,
                printedBanner);
        refreshContext(context);
        afterRefresh(context, applicationArguments);
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass)
                    .logStarted(getApplicationLog(), stopWatch);
        }
        listeners.started(context);
        callRunners(context, applicationArguments);
    } catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }

    try {
        listeners.running(context);
    } catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }
    return context;
}

The operation phase can be roughly divided into the following processes:

Turn on time monitoring

runThe two lines of code at the beginning of the method are used to turn on time monitoring:

StopWatch stopWatch = new StopWatch();
stopWatch.start();

The above code is used to open the Spring Boot application startup time monitoring, and stopWatch.stop();the complete startup time can be calculated with the following .

Open running listener

runThese few lines of code of the method are used to load the Spring Application Run Listener (SpringApplicationRunListener):

SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.started();

getRunListenersMethod source code:

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

The above code SpringFactoriesLoaderfinds all SpringApplicationRunListenerthe declared implementation classes by retrieving META-INF/spring.factories , instantiates them, and then assembles them into the List<SpringApplicationRunListener>running listener collection.

listeners.started();It is used to traverse all SpringApplicationRunListenerthe implementation classes in the set of running listeners and call their startingmethods one by one to broadcast that the Spring Boot application is about to start.

In Spring Boot, the SpringApplicationRunListenerinterface is used to monitor the entire Spring Boot application life cycle. The code is as follows:

public interface SpringApplicationRunListener {
    void starting();

    void environmentPrepared(ConfigurableEnvironment environment);

    void contextPrepared(ConfigurableApplicationContext context);

    void contextLoaded(ConfigurableApplicationContext context);

    void started(ConfigurableApplicationContext context);

    void running(ConfigurableApplicationContext context);

    void failed(ConfigurableApplicationContext context, Throwable exception);
}

These methods correspond to the various stages of the Spring Boot application life cycle:

Method name Corresponding life cycle Spring Boot initial version
starting() Spring application just started 1.0
environmentPrepared(ConfigurableEnvironment) ConfigurableEnvironment is ready, allow it to be adjusted 1.0
contextPrepared(ConfigurableApplicationContext) ConfigurableApplicationContext is ready, allowing it to be adjusted 1.0
contextLoaded(ConfigurableApplicationContext) ConfigurableApplicationContext has been loaded, but it has not yet started 1.0
started(ConfigurableApplicationContext) ConfigurableApplicationContext has been started, and the Spring Bean has been initialized at this time 2.0
running(ConfigurableApplicationContext) Spring application is running 2.0
failed(ConfigurableApplicationContext,Throwable) Spring application failed to run 2.0

We have com.example.demo.linstenera case custom SpringApplicationRunListenerinterface implementation class HelloSpringApplicationRunListener:

public class HelloApplicationRunListener implements SpringApplicationRunListener {

    public HelloApplicationRunListener(SpringApplication application, String[] args) {
    }

    @Override
    public void starting() {
        System.out.println("HelloApplicationRunListener starting......");
    }

    @Override
    public void environmentPrepared(ConfigurableEnvironment environment) {

    }

    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {

    }

    @Override
    public void contextLoaded(ConfigurableApplicationContext context) {

    }

    @Override
    public void started(ConfigurableApplicationContext context) {

    }

    @Override
    public void running(ConfigurableApplicationContext context) {

    }

    @Override
    public void failed(ConfigurableApplicationContext context, Throwable exception) {

    }
}

Through this implementation class, we can output in the console when the Spring Boot application just starts HelloApplicationRunListener starting.......

Because it is implemented based on Spring's factory method, we need to configure this implementation class in the spring.factories file:

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
com.example.demo.run.HelloApplicationRunListener

Start the Spring Boot application and you can see the following output in the console:

Create Environment

runThis line of code in the method is used to create and configure the Environment to be used by the current SpringBoot application (including configuring the PropertySource and Profile to be used):

ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);

We have inferred the application type in the preparation stage. Here, we only need to create the corresponding application environment according to the corresponding application type. The corresponding relationship between the type and the environment is as follows:

  • Web Reactive: StandardReactiveWebEnvironment

  • Web Servlet: StandardServletEnvironment

  • 非 Web: StandardEnvironment

prepareEnvironmentWill be executed in the method listeners.environmentPrepared(environment);, used to traverse and call SpringApplicationRunListenerthe environmentPrepared()methods of all implementation classes, and the broadcast Environment is ready.

Whether to print banner

runThis line of code in the method will determine whether to print the Banner according to our configuration:

Banner printedBanner = printBanner(environment);

Create Context

runThis line of code in the method is used to create ApplicationContext:

context = createApplicationContext();

Different environments correspond to different ApplicationContext:

  • Web Reactive: AnnotationConfigReactiveWebServerApplicationContext

  • Web Servlet: AnnotationConfigServletWebServerApplicationContext

  • 非 Web: AnnotationConfigApplicationContext

Assembly Context

runThis line of code in the method is used to assemble the Context:

prepareContext(context, environment, listeners, applicationArguments, printedBanner);

prepareContextThe source code of the method is as follows:

private void prepareContext(ConfigurableApplicationContext context,
                            ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
                            ApplicationArguments applicationArguments, Banner printedBanner) {
    context.setEnvironment(environment);
    postProcessApplicationContext(context);
    applyInitializers(context);
    listeners.contextPrepared(context);
    if (this.logStartupInfo) {
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }
    // Add boot specific singleton beans
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
    if (printedBanner != null) {
        beanFactory.registerSingleton("springBootBanner", printedBanner);
    }
    if (beanFactory instanceof DefaultListableBeanFactory) {
        ((DefaultListableBeanFactory) beanFactory)
                .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
    }
    // Load the sources
    Set<Object> sources = getAllSources();
    Assert.notEmpty(sources, "Sources must not be empty");
    load(context, sources.toArray(new Object[0]));
    listeners.contextLoaded(context);
}

prepareContextThe method starts with ApplicationContextloading the environment, and then further encapsulates it applyInitializersby executing ApplicationContextInitializerthe method one by one , and calls all the methods of the implementation class, and the broadcast ApplicationContext is ready.initializeApplicationContextSpringApplicationRunListenercontextPrepared

After initializing the IOC container, and calling SpringApplicationRunListenerthe contextLoadedmethod of the implementation class , the broadcast ApplicationContextloading is completed, including @EnableAutoConfigurationvarious automatic configuration classes imported through the import.

Refresh Context

runThis process line for initializing all auto configuration class, and calls ApplicationContextthe refreshmethod:

refreshContext(context);

Broadcast application has started

runThis line of code in the method is used to broadcast that the Spring Boot application has started:

listeners.started(context);

startedThe method will call all SpringApplicationRunListenerthe finishedway broadcast SpringBoot application has been successfully launched.

Execute Runner

runThis line of code in the method callRunners(context, applicationArguments);traverses all the implementation classes of the ApplicationRunnersum CommandLineRunnerand executes its runmethod. We can implement our own ApplicationRunneror CommandLineRunnerto extend the startup process of Spring Boot.

We are com.example.demounder the new runnerpackage, and then create an ApplicationRunnerimplementation class HelloApplicationRunner:

@Component
public class HelloApplicationRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) {
        System.out.println("HelloApplicationRunner: hello spring boot");
    }
}

Here we need to HelloApplicationRunneruse @Componentannotations to register them in the IOC container.

Then create an CommandLineRunnerimplementation class HelloCommandLineRunner:

@Component
public class HelloCommandLineRunner implements CommandLineRunner {
    @Override
    public void run(String... args) {
        System.out.println("HelloCommandLineRunner: hello spring boot");
    }
}

Start the Spring Boot application, you can see the following output just after the application is started:

Broadcast application is running

runThis line of code listeners.running(context);in SpringApplicationRunListenerthe runningmethod is used to call the method to broadcast that the Spring Boot application is running.

When runan exception occurs in the operation of handleRunFailurethe method, the method will be called to handle the exception, and the method that will listeners.failed(context, exception);be called SpringApplicationRunListenerin the failedmethod will broadcast the application startup failure and spread the exception.

All the above broadcast events are broadcast using ApplicationEventMulticasterthe implementation class of Spring's application event broadcaster interface SimpleApplicationEventMulticaster.

 

 

 

Guess you like

Origin blog.csdn.net/u014225733/article/details/100850355