How does SpringBoot start?

This article is about the spring boot startup process sorted out by looking at the spring boot source code. The overall direction is to start with simplicity, not to mention too many complex things. The details of internal implementation are not covered in this article because everyone's ideas and understanding are different. My personal understanding is certainly different from what you see, and there is no use in the cloud and fog expressed at that time.

First of all, I will organize the SpringBoot startup process into the following phases:

  • SpringApplicaiton initialization

    • Review ApplicationContext type
    • Load ApplicationContextInitializer
    • Load ApplicationListener
  • Environment initialization

    • Parsing command line parameters
    • Create Environment
    • Configure Environment
    • Configure SpringApplication
  • ApplicationContext initialization

    • Create ApplicationContext
    • Set ApplicationContext
    • Refresh ApplicationContext
  • Run program entry

Some details that do not affect the main program are omitted. Before looking at the spring boot source code, I have to mention the use and function of spring.factories.

About spring.factories

Spring.factories is a properties file, which is located in the classpath:/META-INF / directory. Each jar package can have a spring.factories file. Spring provides a tool class, springfactorysloader, which is responsible for loading and parsing files. For example, spring-boot-2.2.0.RELEASE.jar contains the spring.factories file in the META-INF directory.

# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader

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

What do you need to know about spring.factories?

  • spring.factories is a properties file
  • The value of key value pairs in spring.factories is a comma separated list of complete class names
  • The key of the key value pair in spring.factories is the full interface name
  • The value of spring.factories key value pair is the implementation class of key
  • spring.factories is loaded by the spring factoriesloader tool class
  • spring.factories is located in the classpath / meta-inf / directory
  • Spring factors loader will load the spring.factors file in the jar package and merge it

After knowing the concept of spring.factories, continue to analyze the start-up of spring boot.

SpringApplicaiton initialization

The entry of Java program can also be started through the main method in the main method of SpringBoot. Only a small amount of code and @ SpringBootApplication annotation are needed to start SpringBoot easily:

@SpringBootApplication
@Slf4j
public class SpringEnvApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringEnvApplication.class, args);
     }

}

SpringApplicaiton initialization is located in the constructor of SpringApplication:

    public SpringApplication(Class<?>... primarySources) {
        this(null, primarySources);
    }
 
    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = deduceMainApplicationClass();
    }

In short, what spring application's constructor does:

  • Assignment of basic variables (resourceLoader, primarySources,...)
  • Review ApplicationContext types such as (Web, Reactive, Standard)
  • Load ApplicationContextInitializer
  • Load ApplicationListener
  • Review startup class (class of main method)

Then we will analyze these steps one by one.

Review ApplicationContext type

Spring boot will review the type of ApplicationContext in the initialization phase by enumerating the static method of the deduceFromClasspath of WebApplicationType:

    static WebApplicationType deduceFromClasspath() {
        if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
                && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
            return WebApplicationType.REACTIVE;
        }
        for (String className : SERVLET_INDICATOR_CLASSES) {
            if (!ClassUtils.isPresent(className, null)) {
                return WebApplicationType.NONE;
            }
        }
        return WebApplicationType.SERVLET;
    }

The WebApplicationType enumeration is used to mark whether a program is a Web program. It has three values:

  • NONE: not a web program
  • Servlet: a servlet based Web program
  • Reactive: a Web program based on reactive

Simply put, the method will judge whether a Web program is available through classpath. The constant in the method is the complete class name:

private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet","org.springframework.web.context.ConfigurableWebApplicationContext" };
private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";
private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";
private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";

For example, if spring boot starter web is introduced through pom.xml file, the classpath will have org.springframework.web.context.ConfigurableWebApplicationContext and javax.servlet.Servlet classes, which determines that the ApplicationContext type of the program is WebApplicationType.SERVLET.

Load ApplicationContextInitializer

Applicationcontext initialize r will execute before refreshing the context. It is generally used to do some extra initialization works, such as adding PropertySource, setting ContextId and so on. It has only one initialization method:

public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
    void initialize(C applicationContext);
}

Spring boot loads the configuration in spring.factories through spring factorysloader to read the value of the key as org.springframework.context.ApplicationContextInitializer. As mentioned earlier, the value of the configuration in spring.facts is the implementation class of key:

org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

The configuration listed above is included in spring-boot-2.2.0.RELEASE.jar. Other jar packages may also configure org.springframework.context.ApplicationContextInitializer to implement additional initialization.

Load ApplicationListener

ApplicationListener is used to listen to ApplicationEvent events. Its initial loading process is similar to that of ApplicationContextInitializer. Some higher priority applicationlisteners will be configured in spring.factories:

# Application Listeners
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.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

The loading process of ApplicationListener is similar to that of ApplicationContextInitializer, which is loaded through springfactorysloader.

Summary

After the initialization phase is complete, you can know the following information:

  • Is ApplicationContext a Web or other type
  • There are some ApplicationContextInitializer implementation classes in SpringApplication
  • There are some implementation classes of ApplicationListener in SpringApplication

Environment initialization

After the initialization, SpringBoot will do many things to prepare the running program. Most of the SpringBoot startup core code is located in the run method of the SpringApplication instance. The general startup process of environment initialization includes:

  • Parsing command line parameters
  • Prepare Environment
  • Setting environment

Of course, there are other operations, such as:

  • Instantiate SpringApplicationRunListeners
  • Print Banner
  • Set exception report
  • ...

These are not important operations will not be explained, you can read the article and then study in detail.

Parsing command line parameters

Command line parameters are passed in by args parameter of main method. SpringBoot establishes a DefaultApplicationArguments class in preparation stage to parse and Save command line parameters. For example -- spring.profiles.active=dev will set the spring.profiles.active property of SpringBoot to dev.

public ConfigurableApplicationContext run(String... args) {
    ...
    ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); 
    ...
}

SpringBoot also puts the received command-line parameters into the Environment, providing a unified attribute abstraction.

Create Environment

The code to create an environment is relatively simple. Different environments are instantiated according to the WebApplicationType mentioned earlier:

private ConfigurableEnvironment getOrCreateEnvironment() {
    if (this.environment != null) {
        return this.environment;
    }
    switch (this.webApplicationType) {
    case SERVLET:
        return new StandardServletEnvironment();
    case REACTIVE:
        return new StandardReactiveWebEnvironment();
    default:
        return new StandardEnvironment();
    }
}

Prepare Environment

Environment consists of Profile and PropertyResolver

  • Profile is the logical grouping of Bean definition. When defining a Bean, you can specify a profile so that SpringBoot will decide whether to register the Bean at runtime according to its profile
  • PropertyResolver is specially used to resolve properties. SpringBoot will load configuration files, system variables and other properties at startup

SpringBoot will call the prepareEnvironment method of SpringApplication when preparing the environment:

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    // Create and configure the environment
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    ConfigurationPropertySources.attach(environment);
    listeners.environmentPrepared(environment);
    bindToSpringApplication(environment);
    ...
    return environment;
}

The prepareEnvironment method does the following roughly:

  • Create an environment
  • Configuration environment
  • Set the properties of SpringApplication

Configure Environment

After the environment is created, some simple configurations will be made for the environment:

protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
    if (this.addConversionService) {
        ConversionService conversionService = ApplicationConversionService.getSharedInstance();
        environment.setConversionService((ConfigurableConversionService) conversionService);
    }
    configurePropertySources(environment, args);
    configureProfiles(environment, args);
}

protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
    
    if (this.addCommandLineProperties && args.length > 0) {
            ...
            sources.addFirst(new SimpleCommandLinePropertySource(args));
            ...
        }
    }
    protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
        Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
        profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
        environment.setActiveProfiles(StringUtils.toStringArray(profiles));
    }
    

The space is limited and some unimportant codes are omitted. The configuration environment is mainly used for:

  • Set ConversionService: for property conversion
  • Add command line parameters to the environment
  • Add additional ActiveProfiles

Spring application property settings

To configure spring application, you need to connect the existing attributes to the spring application instance. For example, the spring.main.banner-mode attribute corresponds to the bannerMode instance attribute. There are three sources of attributes in this step (without customization):

  • environment variable
  • Command line arguments
  • JVM system properties

SpringBoot will bind the attribute prefixed with spring.main to the springapplication instance:

protected void bindToSpringApplication(ConfigurableEnvironment environment) {
    try {
        Binder.get(environment).bind("spring.main", Bindable.ofInstance(this));
    }
    catch (Exception ex) {
        throw new IllegalStateException("Cannot bind to SpringApplication", ex);
    }
}

Summary of Environment initialization

Summarize the general work done in the environmental preparation stage:

  • Create environment based on WebApplicationType enumeration
  • Set ConversionService to convert property variables
  • Add command line parameter args to the environment
  • Add Profiles of external settings to the environment
  • Binding the sprinngapplicationproperty
  • Send environment Prepared event

ApplicationContext initialization

Most of the steps mentioned above are to prepare for the work of ApplicationContext. ApplicationContext provides functions such as loading beans, loading resources, sending events, etc. spring boot does not need to do extra work to create and configure ApplicationContext during startup (too convenient ~ ~).

This article does not intend to go deep into ApplicationContext, because there are many classes related to ApplicationContext, which is not the end of one or two articles. It is recommended to look at the source code of ApplicationContext according to modules and finally integrate them.

Create ApplicationContext

The process of creating ApplicationContext is similar to the basic model of creating environment. According to WebApplicationType, judge the program type and create different ApplicationContext:

protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            switch (this.webApplicationType) {
            case SERVLET:
                contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
                break;
            case REACTIVE:
                contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                break;
            default:
                contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
            }
        }
        catch (ClassNotFoundException ex) {
            throw new IllegalStateException(
                    "Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
        }
    }
    return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

As mentioned earlier, WebApplicationType has three members (SERVLET, REACTIVE, NONE), corresponding to different context types:

  • SERVLET: AnnotationConfigServletWebServerApplicationContext
  • REACTIVE: AnnotationConfigReactiveWebServerApplicationContext
  • NONE: AnnotationConfigApplicationContext

Prepare ApplicationContext

After the creation of ApplicationContext, you need to initialize it, set environment, apply ApplicationContextInitializer, register Source class, etc. the process of preparing Context for SpringBoot can be summarized as follows:

  • Set environment for ApplicationContext (environment created earlier)
  • Basic setting operation settings BeanNameGenerator, ResourceLoader, ConversionService, etc
  • Execute the initialize method of ApplicationContextInitializer (the ApplicationContextInitializer is obtained in the initialization phase)
  • Register command line arguments (springApplicationArguments)
  • Register Banner (springBootBanner)
  • Register sources (classes annotated by @ Configuration)

The code to prepare ApplicationContext 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);
    }
    if (this.lazyInitialization) {
        context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
    }
    // 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);
}

Pay attention to the step of registering sources. Sources is the class of @ Configuration annotation SpringBoot to register Bean according to the provided sources. The basic principle is to parse annotation metadata, create BeanDefinition and register it in ApplicationContext.

Refresh ApplicationContext

If spring boot is a car, all the previous operations are to open the door, fasten the seat belt and other basic operations. Refreshing the ApplicationContext is the ignition. Not refreshing the ApplicationContext just saves the definition of a Bean and the postprocessor doesn't really run. It is very important to refresh ApplicationContext. To understand ApplicationContext, you need to see the source code of the refresh operation,
Here are the basic steps:

  • Prepare to refresh (verify properties, set listener)
  • Initialize BeanFactory
  • Execute BeanFactoryPostProcessor
  • Register BeanPostProcessor
  • Initialize MessageSource
  • Initialize event broadcast
  • Register ApplicationListener
  • ...

There are many steps to refresh the process, and the associated class libraries are relatively complex. It is recommended to read other auxiliary class libraries first and then refresh the source code, and you will get twice the result with half the effort.

Run program entry

After the context refresh is completed, the Spring container can be fully used. Next, Spring boot will execute ApplicationRunner and CommandLineRunner. The two interfaces have similar functions, but only one run method receives different parameters. By implementing them, you can customize the startup modules, such as start dubbo, gRPC and so on.

The calling codes of ApplicationRunner and CommandLineRunner are as follows:

private void callRunners(ApplicationContext context, ApplicationArguments args) {
    List<Object> runners = new ArrayList<>();
    runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
    runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
    AnnotationAwareOrderComparator.sort(runners);
    for (Object runner : new LinkedHashSet<>(runners)) {
        if (runner instanceof ApplicationRunner) {
            callRunner((ApplicationRunner) runner, args);
        }
        if (runner instanceof CommandLineRunner) {
            callRunner((CommandLineRunner) runner, args);
        }
    }
}

After the call runners are executed, the spring boot startup process is completed.

summary

By looking at the source code of spring application, it is found that the start source code of spring boot is easy to understand. It is mainly to provide an initialization entry for ApplicationContext, which saves developers from configuring ApplicationContext. The core function of spring boot is auto configuration. Next time you analyze the source code of spring boot autoconfig, you need to fully understand that the source code of spring boot is less.

After reading the source code of spring application, there are still some problems worth thinking about:

  • SpringBoot is the process of starting Tomcat
  • Spring boot auto configuration principle
  • SpringBoot Starter customization
  • Implementation principle of BeanFactoryPostProcessor and BeanPostProcessor
  • ...





Every day, architecture digest is a great article in architecture field, which involves application architecture (high availability, high performance, high stability), big data, machine learning and other hot fields of first-line Internet companies.

Tags: Java Spring SpringBoot Attribute

Posted on Tue, 05 Nov 2019 03:20:57 -0500 by flaab