Analysis of Spring boot startup process

Posted by JustinMs66@hotmail.com on Fri, 24 Dec 2021 14:13:09 +0100

Someone has always done something for you

We are often amazed that Spring boot greatly simplifies the developer's development process and releases us from the hard pressed xml configuration, so we can fish more happily.

But have you ever thought about a few lines of simple boot programs and how Spring boot does it?

The following program can be said to be the most fundamental Spring boot startup class. We only need a main method to focus on business development.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class HelloSpringBootStarterTestApplication {

    public static void main(String[] args) {
        SpringApplication.run(HelloSpringBootStarterTestApplication.class, args);
    }
}

1. Division of springboot startup phase

The Spring boot startup process can be divided into two parts

1. Spring boot configuration process. [mainly the construction method of SpringApplication class]

2. Spring boot startup process. [real core startup class]

2. Spring boot configuration process

First, the SpringApplication() instantiation method will be called, which is mainly to create the SpringApplication instance object. This method is mainly to call the overloaded constructor.

// This method simply calls the overloaded instantiation constructor
public SpringApplication(Class<?>... primarySources) {
  this(null, primarySources);
}

Next, load some system resources. Here is the main entrance to load user-defined configuration. The main process is divided into four steps:

1. Judge the type of application, NON, SERVLET and REACTIVE. Under normal circumstances, we are all SERVLET services.

2. Go to spring Factories find ApplicationContextInitializer, mainly to read the file information spring-boot-2.3 12.RELEASE. jar!/ META-INF/spring.factories, all initializers of orders in this file will be read at this time. Here, through debug, it is found that there are mainly 7 container initializers.

In the future, all such getSpringFactoriesInstances() (this method will be analyzed in detail below) need to find initializers (such as container initialization and listener initialization) from the corresponding spring.factories file

3. Similarly. Go to spring Factories find the listener, and the idea of obtaining is the same as that of Initializer.

4. Find the class with main method, and here we can find our main program class.

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
  this.resourceLoader = resourceLoader;
  this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
  
  //  1. WebApplicationType is an enumeration class, including none, SERVLET and reactive. The downstream webApplicationType is SERVLET
  this.webApplicationType = WebApplicationType.deduceFromClasspath();
  
  // 2. Go to spring Factories cannot find ApplicationContextInitializer
  setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
  
  // 3. Go to spring Factories find the listener, and the idea of obtaining is the same as that of Initializer
  setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
  
  // 4. Find the class with main method, and here we can find our main program class.
  this.mainApplicationClass = deduceMainApplicationClass();
}

1. Getspringfactoryesinstances() that cannot be wrapped around

It mainly loads various pre preset initializers. The main ideas are as follows:

1. How to get the class loader in the ApplicationContext scenario? The essence is by calling classutils getDefaultClassLoader(); Get default class loader

2. Get various configurations to spring The initializer in factories is stored in the set set to prevent duplication

3. Start creating the initializer based on the name obtained in the previous step. The implementation method is through BeanUtils Instantiateclass().

4. The initializers are sorted according to the Order annotation on each initializer.

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
  return getSpringFactoriesInstances(type, new Class<?>[] {});
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
  // 1. Get class loader in ApplicationContext scenario 
  ClassLoader classLoader = getClassLoader();
  
  // 2. Get various configurations to spring The initializer in factories and stored in the set collection
  Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
  
  // 3. Start to create the initializer according to the name obtained in the previous step
  List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
  
  // 4. Sort initializers
  AnnotationAwareOrderComparator.sort(instances);
  return instances;
}

Draw a simple sequence diagram to illustrate how Spring boot is configured before startup

3. Start of running Spring application

In fact, I've said so much just to introduce the Spring configuration process, and then I'll start the real program running.

The operation here includes the operation of the application and the process of creating and initializing the container (Spring container initialization process). I roughly divide the startup process into

  1. Start running.

  2. Set Java awt. The headless property. Its function is unknown at present.

  3. Get all spring applicationrunlisteners [to facilitate event awareness for all listeners]**

  4. A call to starting() indicates that the container has started

  5. Wrap the parameters used in the container (generally passed in through the startup command) into ApplicationArguments objects,

  6. Prepare environment basic configuration

    1. Return or create basic environment information object
    2. Decide which profile to use and read profile values
    3. The listener calls listener environmentPrepared(); Notify all listeners that the current environment is ready
    4. Binding environment information
  7. Print banner (that is, the Spring boot ID printed every time you start)

  8. Create IOC container [to the point of startup] this step is only to create an empty container

    1. Create a container based on the project type (Servlet)
    2. It mainly creates the ConfigurableApplicationContext container
  9. Prepare container information

    1. Save environment information
    2. Post processing flow of IOC container (the classic postProcessApplicationContext() method in Spring container takes place in this step)
    3. Start initializing all initializers; applyInitializers;
      1. Traverse all applicationcontextinitializers (remember the seven initializers loaded from spring.factories at the beginning? Each initializer is initialized in this step). Call initialize. To initialize the ioc container and extend the function
    4. Traverse all listeners and call contextPrepared. EventPublishRunListenr; Notify all listeners * * * * contextPrepared
    5. Print the startup log, which can also be configured
    6. Get the beanFactory from the container (students familiar with Spring should just be, which is actually a simpler version of ApplicationContext), and inject the parameters passed in from the command line into the ConfigurableListableBeanFactory as a bean component.
    7. All listeners call contextLoaded. Notify all listeners of the contextLoaded event;
  10. Refresh the IOC container. This step comes to the refresh() method at the bottom of Spring. AbstractApplicationContext#refresh() method is mainly used to refresh the container

    The content here is too long. In a subsequent article, we only need to know that after this step, we have basically prepared the contents of all containers.

  11. All listeners call listeners started(context); Notify all listeners that the container has been successfully started

  12. Call all runners

    1. Gets the ApplicationRunner in the container
    2. Gets the CommandLineRunner in the container
    3. Merge all runner s and sort by @ Order
    4. Traverse all runner s. Call the run method, which is suitable for doing one-time work when starting with some containers
  13. Call the running() method of listeners to notify all listeners that the container is starting.

At this point, the entire Spring boot application is fully started.

Simple browsing of source code

public ConfigurableApplicationContext run(String... args) {
  // 1. Start running timing
  StopWatch stopWatch = new StopWatch();
  stopWatch.start();
  
  ConfigurableApplicationContext context = null;
  // 2. Set Java awt. The headless property. Its function is unknown at present.
  configureHeadlessProperty();
  
  // 3. To create SpringApplicationRunListeners, the essence is to / meta-inf / spring Listeners in factories are wrapped as spring application runlisteners
  SpringApplicationRunListeners listeners = getRunListeners(args);
  
  // 4. Calling starting() indicates that the container has started
  listeners.starting();
  try {
    
    // 5. Wrap the parameters used in the container (generally passed in through the startup command) into ApplicationArguments objects,
    ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
    
    // 6. Prepare basic environment configuration (including deciding which configuration file to use)
    ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
    configureIgnoreBeanInfo(environment);
    // 7. Print banner (that is, the Spring boot logo printed every time you start)
    Banner printedBanner = printBanner(environment);
    // 8. Create IOC container [to the point of startup] at this step, it is only an empty container created
    context = createApplicationContext();
    
    // 9. Prepare container information
    prepareContext(context, environment, listeners, applicationArguments, printedBanner);
    
    // 10. Refresh IOC container
    refreshContext(context);
    

    afterRefresh(context, applicationArguments);
    stopWatch.stop();
    if (this.logStartupInfo) {
      new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
    }
    
    // 11. Notify all listeners that the container has been started successfully
    listeners.started(context);
    
    // 12. Call all runners
    callRunners(context, applicationArguments);
  }
  catch (Throwable ex) {
    handleRunFailure(context, ex, listeners);
    throw new IllegalStateException(ex);
  }    
  // 13. Tell all listeners that the container has started and is running
  listeners.running(context);
  
  // Exception handling situations that do not need to be concerned are temporarily ignored here
  return context;
}

summary

So far, the Spring boot startup process can only be said to have been roughly analyzed. Here, due to the length and avoid being trapped in too many details, only the main process is analyzed. In fact, if each process is separated, it can be divided into multiple articles, such as the refreshcontext (context) in step 10, which is the core of the Spring source code, It includes how to initialize the container and how to set various components. A separate analysis article will be given later.

The book about Spring and Spring boot is recommended again. Although the knowledge in the book has been long and will not be updated, both the way it describes the problem and the way it explains it eloquently are worthy of the word "uncover the secret".