Source code analysis: spring boot startup process (I)

Time:2022-5-5

Write in front

Because I also read some corresponding spring related source code reading videos and articles first, but I still know a little about this part and am lazy, so I urge myself to read it as a whole in the way of recording. There will be many omissions. Please forgive me.

The ultimate goal of the individual is to cover the comments in every line of code of all start-up processes. If you think there is redundancy, please point out in the comments.

Overall reading

Because I am also the first time to read spring related source code by myself, there may be a rough reading part, which will also be recorded accordingly. If there is a general context, skip itOverall readingThis chapter, then start right away.

Environment construction

First, start with the startup class. Let’s build the simplest spring boot environment. The source code version of this reading is2.6.0-SNAPSHOT

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.zhisan</groupId>
    <artifactId>spring-boot-study</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.0-SNAPSHOT</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <repositories>
        <repository>
            <id>spring-snapshots</id>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots><enabled>true</enabled></snapshots>
        </repository>
        <repository>
            <id>spring-milestones</id>
            <url>https://repo.spring.io/milestone</url>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-snapshots</id>
            <url>https://repo.spring.io/snapshot</url>
        </pluginRepository>
        <pluginRepository>
            <id>spring-milestones</id>
            <url>https://repo.spring.io/milestone</url>
        </pluginRepository>
    </pluginRepositories>


</project>
package com.zhisan;

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

@SpringBootApplication
public class AppRun {

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

Start with class

Then start our apprun. I believe many people start with @ springbootapplication. Indeed, if you deal with the interview, you can kill most small companies by explaining this part. But this time we are completely analyzing the source code, so our focus is still on the following aspects.

  • How to run it
  • How to obtain bean and other related configurations after running

This is the simplest purpose of our rough overall reading.

First, let’s pull out several important parts.

  • Springapplication (class initialization)
  • SpringApplication. Run (method call)

Startup class

org.springframework.boot.SpringApplication

First, locate this construction method and see what it prepares for us.

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        //Resource loader = null
        this.resourceLoader = resourceLoader;
        //Judge whether the main resource is empty. If it is empty, assert and throw the exception illegalargumentexception
        Assert.notNull(primarySources, "PrimarySources must not be null");
        //The primary resources are de duplicated and put into primary sources
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        //Infer the class path according to the path, and finally get the result "servlet"
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        //Get bootstrap registry initializers from Spring Factory
        this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
        //Get spring factory instance - > application context initializer
        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
        //Get spring factory instance - > Application listener
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        //Derive the main application class
        this.mainApplicationClass = deduceMainApplicationClass();
    }

After a simple reading, we come to a conclusion that this part is the acquisition and setting of some basic information, which is similar to the function of environment preparation, so we finally get several resources.

  • List of main resources
  • Web app type Servlet
  • Application context initializer

    • org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
  • Listener application

    • 0 = “org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor”
    • 1 = “org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor”
    • 2 = “org.springframework.boot.env.RandomValuePropertySourceEnvironmentPostProcessor”
    • 3 = “org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor”
    • 4 = “org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor”
    • 5 = “org.springframework.boot.reactor.DebugAgentEnvironmentPostProcessor”
    • 6 =”org.springframework.boot.autoconfigure.integration.IntegrationPropertiesEnvironmentPostProcessor”
  • The full class name of the main application is com zhisan. AppRun

We pay more attention to the loaded part of the application listener, because we don’t know how these contents are loaded for referenceLoad spring factoryChapter.

Startup class startup method

org.springframework.boot.SpringApplication#run(java.lang.String…)

After initializing springapplication, you can run its run method. Most of the overall running logic is also here. We focus on analysis in this part.

public ConfigurableApplicationContext run(String... args) {
    //Start a simple stopwatch
    //Tip: used for timing and can be ignored
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    //Create context guidance
    DefaultBootstrapContext bootstrapContext = createBootstrapContext();
    //Define configurable application context variables
    ConfigurableApplicationContext context = null;
    //Configure headless properties
    configureHeadlessProperty();
    //Get running listener
    SpringApplicationRunListeners listeners = getRunListeners(args);
    //Start listener
    listeners.starting(bootstrapContext, this.mainApplicationClass);
    try {
        //Default application parameters
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        //Prepare the environment
        ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
        //Configure ignore bean information
        configureIgnoreBeanInfo(environment);
        //Print banner spring icon (customizable)
        Banner printedBanner = printBanner(environment);
        //Create application context
        context = createApplicationContext();
        //Set application startup
        context.setApplicationStartup(this.applicationStartup);
        //Prepare context
        prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
        //Refresh context
        //This will involve starting spring
        //Start Web Service
        refreshContext(context);
        //Execute after refresh
        //Tip: not implemented for scalability
        afterRefresh(context, applicationArguments);
        //Clock end
        stopWatch.stop();
        //Time consuming printing
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
        }
        listeners.started(context);
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, listeners);
        throw new IllegalStateException(ex);
    }

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

Line by line reading

Inferring web types from Classpaths

org.springframework.boot.WebApplicationType#deduceFromClasspath

static WebApplicationType deduceFromClasspath() {
    //Judge org springframework. web. reactive. Dispatcherhandler exists and org springframework. web. servlet. Dispatcher servlet and org glassfish. jersey. servlet. Servletcontainer does not exist
    //Reactive mode is adopted
    if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
            && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
        return WebApplicationType.REACTIVE;
    }
    //If "javax. Servlet. Servlet" and "org. Springframework. Web. Context. Configurablewebapplicationcontext" exist, none will be returned
    for (String className : SERVLET_INDICATOR_CLASSES) {
        if (!ClassUtils.isPresent(className, null)) {
            return WebApplicationType.NONE;
        }
    }
    //If no match is found, the default servlet is used
    return WebApplicationType.SERVLET;
}

Get bootstrap registry initializers from Spring Factory

org.springframework.boot.SpringApplication#getBootstrapRegistryInitializersFromSpringFactories

private List<BootstrapRegistryInitializer> getBootstrapRegistryInitializersFromSpringFactories() {
    //Initialization list
    ArrayList<BootstrapRegistryInitializer> initializers = new ArrayList<>();
    getSpringFactoriesInstances(Bootstrapper.class).stream()
            .map((bootstrapper) -> ((BootstrapRegistryInitializer) bootstrapper::initialize))
            .forEach(initializers::add);
    //Boot registry initializer
    initializers.addAll(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
    return initializers;
}

Load spring factory

org.springframework.core.io.support.SpringFactoriesLoader#loadSpringFactories

General Factory loading mechanism used internally within the framework.
Spring factoryesloader loads and instantiates factories of a given type from the “meta-inf / spring. Factories” file, which may exist in multiple jar files in the classpath. spring. The factories file must be in the format of properties, where key is the fully qualified name of the interface or abstract class, and value is a comma separated list of implementation class names. For example:
example.MyService=example.MyServiceImpl1,example.MyServiceImpl2
Where example Myservice is the interface name. Myserviceimpl1 and myserviceimpl2 are two implementations.

Above isorg.springframework.core.io.support.SpringFactoriesLoaderAfter reading this part of the introduction, I have a bit of an eye, so here is the line by line analysis of a method in this class.

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
    //Get the contents of the corresponding class loader
    Map<String, List<String>> result = cache.get(classLoader);
    //If it exists, the content in the class loader is returned
    if (result != null) {
        return result;
    }

    //If it does not exist, initialize the contents in the class loader
    result = new HashMap<>();
    try {
        //Get resources - > meta-inf / spring Factories list
        //There may be more than one meta-inf / spring Factories file
        Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
        //Cyclic loading
        while (urls.hasMoreElements()) {
            //Get meta-inf / spring Factories file URL address
            URL url = urls.nextElement();
            //Load resources
            UrlResource resource = new UrlResource(url);
            //Load resources中配置
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            //The circular configuration is similar to the form of properties key: value
            for (Map.Entry<?, ?> entry : properties.entrySet()) {
                String factoryTypeName = ((String) entry.getKey()).trim();
                //Comma separated list to string array
                String[] factoryImplementationNames =
                        StringUtils.commaDelimitedListToStringArray((String) entry.getValue());    
                //Loop sub items into the list
                for (String factoryImplementationName : factoryImplementationNames) {
                    result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
                            .add(factoryImplementationName.trim());
                }
            }
        }

        //List de duplication
        result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
                .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
        //Save the list
        cache.put(classLoader, result);
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load factories from location [" +
                FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
    return result;
}

Specifically analyze this part of the code, and obtain the key value pairs from the cache according to different class loaders. If they exist, the contents in the class loader will be returned without repeated reading. If the key value pair is not found, get the meta-inf / spring The contents in the factories file are parsed one by one, and finally put into the cache, waiting for the next acquisition.

It can be seen from this part of the method that springboot sets some default interface classes and specific implementation classes into the configuration file, which is similar to the way of the properties file often used in the past.

For example:

example.MyService=example.MyServiceImpl1,example.MyServiceImpl2

The former is the interface class, and the latter is the specific implementation method of the interface.

Get spring factory instance

org.springframework.boot.SpringApplication#getSpringFactoriesInstances(java.lang.Class<T>, java.lang.Class<?>[], java.lang.Object…)

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
    //Get class loader
    ClassLoader classLoader = getClassLoader();
    //Use names and make them unique to prevent duplication
    // Use names and ensure unique to protect against duplicates
    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    //Create spring factory instance
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

Get class loader

org.springframework.boot.SpringApplication#getClassLoader

public ClassLoader getClassLoader() {
    if (this.resourceLoader != null) {
        return this.resourceLoader.getClassLoader();
    }
    return ClassUtils.getDefaultClassLoader();
}

Get spring factory instance

org.springframework.boot.SpringApplication#createSpringFactoriesInstances

private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
            ClassLoader classLoader, Object[] args, Set<String> names) {
    //Create instance list
    List<T> instances = new ArrayList<>(names.size());
    //Loop list
    for (String name : names) {
        try {
            //Get class by class name
            Class<?> instanceClass = ClassUtils.forName(name, classLoader);
            //Judge whether the subclass is assignable
            Assert.isAssignable(type, instanceClass);
            //Get constructor
            Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
            //Instantiate object
            T instance = (T) BeanUtils.instantiateClass(constructor, args);
            //Put the instantiation result into the instantiation list
            instances.add(instance);
        }
        catch (Throwable ex) {
            throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
        }
    }
    return instances;
}

Create boot context

org.springframework.boot.SpringApplication#createBootstrapContext

private DefaultBootstrapContext createBootstrapContext() {
    DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
    this.bootstrapRegistryInitializers.forEach((initializer) -> initializer.initialize(bootstrapContext));
    return bootstrapContext;
}

Instantiation class

org.springframework.beans.BeanUtils#instantiateClass(java.lang.reflect.Constructor<T>, java.lang.Object…)

public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws BeanInstantiationException {
    Assert.notNull(ctor, "Constructor must not be null");
    try {
        ReflectionUtils.makeAccessible(ctor);
        if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(ctor.getDeclaringClass())) {
            return KotlinDelegate.instantiateClass(ctor, args);
        }
        else {
            Class<?>[] parameterTypes = ctor.getParameterTypes();
            Assert.isTrue(args.length <= parameterTypes.length, "Can't specify more arguments than constructor parameters");
            Object[] argsWithDefaultValues = new Object[args.length];
            for (int i = 0 ; i < args.length; i++) {
                if (args[i] == null) {
                    Class<?> parameterType = parameterTypes[i];
                    argsWithDefaultValues[i] = (parameterType.isPrimitive() ? DEFAULT_TYPE_VALUES.get(parameterType) : null);
                }
                else {
                    argsWithDefaultValues[i] = args[i];
                }
            }
            return ctor.newInstance(argsWithDefaultValues);
        }
    }
    catch (InstantiationException ex) {
        throw new BeanInstantiationException(ctor, "Is it an abstract class?", ex);
    }
    catch (IllegalAccessException ex) {
        throw new BeanInstantiationException(ctor, "Is the constructor accessible?", ex);
    }
    catch (IllegalArgumentException ex) {
        throw new BeanInstantiationException(ctor, "Illegal arguments for constructor", ex);
    }
    catch (InvocationTargetException ex) {
        throw new BeanInstantiationException(ctor, "Constructor threw exception", ex.getTargetException());
    }
}

This is partly because there are too many spring source codes involved. The most important thing is
ctor.newInstance(argsWithDefaultValues)
This method instantiates an object with a constructor.

Spring refresh

org.springframework.context.support.AbstractApplicationContext#refresh

public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

        //Ready to refresh
        //Tip: some setting parameters, but don't take a closer look
        prepareRefresh();

        //Tell subclasses to refresh internal bean factories
        //Get refresh bean factory
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        //Prepare bean factory
        prepareBeanFactory(beanFactory);

        try {
            //Allows post-processing of bean factories in context subclasses.
            //Tip: this part involves the startup of a web server, such as a servlet
            postProcessBeanFactory(beanFactory);

            StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
            //Call the factory processor registered as a bean in the context.
            invokeBeanFactoryPostProcessors(beanFactory);

            //Register the bean processor created by the intercepting bean.
            registerBeanPostProcessors(beanFactory);
            beanPostProcess.end();

            //Initialize the message source for this context.
            initMessageSource();

            //Initialize the event multicast for this context.
            initApplicationEventMulticaster();

            //Initializes other special beans in subclasses of a specific context.
            onRefresh();

            //Check the listener beans and register them.
            registerListeners();

            //Instantiate all remaining (non delayed initialization) singletons.
            finishBeanFactoryInitialization(beanFactory);

            //The last step: publish the corresponding event.
            finishRefresh();
        }

        catch (BeansException ex) {
            if (logger.isWarnEnabled()) {
                logger.warn("Exception encountered during context initialization - " +
                        "cancelling refresh attempt: " + ex);
            }

            // Destroy already created singletons to avoid dangling resources.
            destroyBeans();

            // Reset 'active' flag.
            cancelRefresh(ex);

            // Propagate exception to caller.
            throw ex;
        }

        finally {
            // Reset common introspection caches in Spring's core, since we
            // might not ever need metadata for singleton beans anymore...
            resetCommonCaches();
            contextRefresh.end();
        }
    }
}

This part is not enough to read, and the part involving spring is too deep.

reference material

java. awt. Headless mode
The most authoritative Spring + spring boot source code analysis in the whole network! Ali senior architect takes 450 minutes to let you master spring and spring boot.

Any mistakes in the article are welcome to correct! Common progress!

Finally, make a small advertisement. If you are interested in java development, you can join the group to learn and communicate together!

QQ:425343603