Source code analysis: Spring Boot startup process (1)

Source code analysis: Spring Boot startup process (1)

Write in front

Because I also watched some corresponding Spring-related source code reading videos and articles first, but I still know a little about this part, and I am relatively lazy, so I urge myself to read it in a record way. There will definitely be many omissions. Please excuse me.

My ultimate goal is to cover the comments in every line of code in all the startup processes. If you think there is a redundant place, please point it out in the comments.

Whole reading

Because I am also the first time to read Spring related source code in the true sense, there may be a rough reading part, which will also be recorded accordingly. If you have a general context, skip reading this chapter as a whole . , Then start right away.

Environment setup

1. start with the startup class. Let's build the simplest Spring Boot environment. The source code version read this time is 2.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 > copy code
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); } } Copy code

Start the class

Then start our AppRun. I believe many people started from @SpringBootApplication. Indeed, if you are dealing with an interview, you only need to explain this part of the content to kill most small companies, but this time we are completely source code Analysis, so our focus is still on the following aspects.

  • How to run him
  • After running, how to obtain related configuration such as beans

This is the simplest purpose of our rough overall reading this time.

First of all, we took away several important parts.

  • SpringApplication (class initialization)
  • SpringApplication.run (method call)

Start class

org.springframework.boot.SpringApplication

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

public SpringApplication (ResourceLoader resourceLoader, Class<?>... primarySources) { //Resource Loader = null this .resourceLoader = resourceLoader; //Determine whether the main resource is empty, if it is empty, assert that an exception is thrown IllegalArgumentException Assert.notNull( primarySources, "PrimarySources must not be null" ); //Deduplicate the main resources and put them in primarySources this .primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); //Infer the class path according to the path, and finally get Result "SERVLET" this .webApplicationType = WebApplicationType.deduceFromClasspath(); //Get Bootstrap Registry Initializers this from Spring Factory.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories(); //Get Spring factory instance -> application context initializer setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); //Get a Spring factory instance -> application listener setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); //Derive the main application class this .mainApplicationClass = deduceMainApplicationClass(); } Copy code

After a simple reading, we came to a conclusion that this part is the acquisition and setting of some basic information, similar to the function of environment preparation, then the few resources we finally get are.

  • List of main resources
  • Web App type Servlet
  • Application context initializer
    • org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
  • Application listener
    • 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 complete class name of the main application com.zhisan.AppRun

We are more concerned about the loaded part of the application listener, because we do not yet know how the content is loaded, you can refer to the loading Spring factory chapter.

Startup method

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

After initializing SpringApplication, you can run its run method, and most of the overall running logic is also here. Our part focuses on analysis.

public ConfigurableApplicationContext run (String... args) { //Start a simple stopwatch //tip: for timing, you can ignore StopWatch stopWatch = new StopWatch(); stopWatch.start(); //Create a boot context DefaultBootstrapContext bootstrapContext = createBootstrapContext(); //Define configurable application context variables ConfigurableApplicationContext context = null ; //Configure headless properties configureHeadlessProperty(); //Get running listener SpringApplicationRunListeners listeners = getRunListeners(args); //Start the 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 to ignore Bean information configureIgnoreBeanInfo(environment); //Print banner Spring icon (customizable) Banner printedBanner = printBanner(environment); //Create application context context = createApplicationContext(); //Set the application startup context.setApplicationStartup( this .applicationStartup); //Prepare the context prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); //Refresh the context //This will involve starting Spring //Starting the web service refreshContext(context); //Execute after refresh //tip: not implemented for scalability afterRefresh(context, applicationArguments); //end of clock 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; } Copy code

Read line by line

Infer the web type from the classpath

org.springframework.boot.WebApplicationType#deduceFromClasspath

static WebApplicationType deduceFromClasspath () { //Determine that org.springframework.web.reactive.DispatcherHandler exists, and that org.springframework.web.servlet.DispatcherServlet and org.glassfish.jersey.servlet.ServletContainer do not exist //Use the reactive method if ( ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null ) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null ) && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null )) { return WebApplicationType.REACTIVE; } //If one of "javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext" exists, it will return NONE for (String className: SERVLET_INDICATOR_CLASSES) { if (!ClassUtils.isPresent(className, null )) { return WebApplicationType .NONE; } } //If none of them match, use the default Servlet return WebApplicationType.SERVLET; } Copy code

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 the registry initialization program initializers.addAll(getSpringFactoriesInstances(BootstrapRegistryInitializer.class)); return initializers; } Copy code

Load the Spring factory

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

The general factory loading mechanism used internally in the framework.

SpringFactoriesLoader loads and instantiates factories of a given type from the "META-INF/spring.factories" file. These files may exist in multiple JAR files in the classpath. The spring.factories file must be in Properties format, 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.

The above is the class introduction of org.springframework.core.io.support.SpringFactoriesLoader . If you read this part of the introduction, it is already a bit eye-catching. Then I will analyze the line by line of a specific 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 exists, return the content in the class loader if (result != null ) { return result; } //If it does not exist, initialize the content in the class loader result = new HashMap<>(); try { //Get resources -> META-INF/spring.factories list //There may be multiple META-INF/spring.factories files Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION); //Load loop while (urls.hasMoreElements()) { //Get the URL address of the META-INF/spring.factories file URL url = urls.nextElement(); //Load resource UrlResource resource = new UrlResource(url); //Load resource configuration Properties properties = PropertiesLoaderUtils.loadProperties(resource); //Loop configuration is similar to properties key: value form 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 the sub-items in value to 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; } Copy code

Specifically analyze this part of the code, get the key-value pairs from the cache according to different class loader, and return the content in the class loader if it exists, without repeating reading. If the key-value pair is not found, the content in the META-INF/spring.factories file that can be obtained in the class loader is obtained, parsed one by one and finally placed in the cache, waiting for the next acquisition.

This part of the method can be seen, SpringBoot sets some default interface classes and specific implementation classes to the configuration file, similar to the way the properties file is often used before.

E.g:

example.MyService=example.MyServiceImpl1,example.MyServiceImpl2

The former is an interface class, and the latter is a concrete realization method of the interface.

Get a Spring factory instance

org.springframework.boot.SpringApplication#getSpringFactoriesInstances(java.lang.Class, 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 ensure unique to protect against duplicates Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); //Create a Spring factory instance List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; } Copy code

Get the class loader

org.springframework.boot.SpringApplication#getClassLoader

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

Get a 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 a list of instances List<T> instances = new ArrayList<>(names.size()); //Loop list for (String name: names) { try { //Get Class according to the class name Class<?> instanceClass = ClassUtils.forName(name, classLoader); //Determine whether the subclass can be allocated Assert.isAssignable(type, instanceClass); //Get the constructor Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes); //instantiate the object T instance = (T) BeanUtils.instantiateClass(constructor, args); //Put the instantiation result in the instantiation list instances.add(instance); } catch (Throwable ex) { throw new IllegalArgumentException( "Cannot instantiate " + type + ": " + name, ex); } } return instances; } Copy code

Create boot context

org.springframework.boot.SpringApplication#createBootstrapContext

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

Instantiated class

org.springframework.beans.BeanUtils#instantiateClass(java.lang.reflect.Constructor, 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()); } } Copy code

This part is because there is too much Spring source code involved, the most important thing is the ctor.newInstance(argsWithDefaultValues) method, which uses the constructor to instantiate the object.

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, don t take a closer look prepareRefresh(); //Tell the subclass to refresh the internal bean factory //Get the refreshed bean factory ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); //prepare bean factory prepareBeanFactory(beanFactory); try { //Allow post-processing of the bean factory in the context subclass. //tip: This part involves the startup of the 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 handler created by the intercepting bean. registerBeanPostProcessors(beanFactory); beanPostProcess.end(); //Initialize the message source of this context. initMessageSource(); //Initialize the event multicaster for this context. initApplicationEventMulticaster(); //Initialize other special beans in a specific context subclass. onRefresh(); //Check the listener beans and register them. registerListeners(); //Instantiate all remaining (non-lazy initialized) 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(); } } } Copy code

This part is not too much to read, the part involving Spring is too deep.

Reference

java.awt.headless mode The most authoritative Spring+Spring Boot source code analysis on the whole network! Ali senior architects will let you master Spring and Spring Boot in 450 minutes.

Any errors in the article are welcome to correct! Make progress together!

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

QQ: 425343603