1, Background
Spring Boot 2.6.0 is used to interpret the source code in this article. There will be slight changes between different versions. You can make your own differences. The dependencies used in this article are as follows.
pom.xml
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.0</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.alian</groupId> <artifactId>springboot</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springboot</name> <description>Spring Boot Source code interpretation</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
2, Spring application instantiation
2.1. Instantiation method entry
package com.alian.springboot; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class SpringbootApplication { public static void main(String[] args) { //Directly call the static run method of SpringApplication SpringApplication.run(SpringbootApplication.class, args); } }
the specific path of the class where the method in the following is located: org.springframework.boot.SpringApplication. The first parameter primarySource: the main resource class loaded, and the second parameter args: the parameters passed to the application
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) { return run(new Class<?>[] { primarySource }, args); } public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args); } public SpringApplication(Class<?>... primarySources) { this(null, primarySources); } public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { //It is known from the above method that the resourceLoader here is null this.resourceLoader = resourceLoader; //Judge that primarySources cannot be empty. From the main method, we know that primarySources is SpringbootApplication.class Assert.notNull(primarySources, "PrimarySources must not be null"); //Put primarySources into the Set collection (the global variable primarySources of SpringApplication) this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); //Infer the application type from the classpath and put it into the global variable webApplicationType of spring application this.webApplicationType = WebApplicationType.deduceFromClasspath(); //Get the implementation class of bootstrap registryinitializer from META-INF/spring.factories file //And use reflection to create an object and return it to the global variable bootstrap registryinitializers, List collection of spring application this.bootstrapRegistryInitializers = new ArrayList<>(getSpringFactoriesInstances(BootstrapRegistryInitializer.class)); //Get the implementation class of ApplicationContextInitializer from META-INF/spring.factories file (from cache) //The object is created by reflection and returned to the global variable initializers and List collection of spring application setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); //Get the implementation class of ApplicationListener from META-INF/spring.factories file (from cache) //The object is created by reflection and returned to the global variable listeners and List collection of spring application setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); //By getting the current call stack, find the class of the entrance method main, and put it into the global variable mainApplicationClass of SpringApplication. this.mainApplicationClass = deduceMainApplicationClass(); }
- Infer application type (SERVLET in this article)
- Get the boot registration initializer through the Spring factory loading mechanism (get and set it to the cache)
- Get and set the initializer through the Spring factory loading mechanism (get in the cache)
- Get and set the listener through the Spring factory loading mechanism (get in the cache)
- Infer main application class
2.2. Infer application type
let's see how to infer the application type
//Infer the application type from the classpath and put it into the global variable webApplicationType of SpringApplication this.webApplicationType = WebApplicationType.deduceFromClasspath();
the specific path of the class where this method is located: org.springframework.boot.WebApplicationType
package org.springframework.boot; public enum WebApplicationType { /** * Non WEB project */ NONE, /** * SERVLET WEB project */ SERVLET, /** * Responsive WEB project */ REACTIVE; 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"; 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; } //Other code omitted }
in fact, it is to judge whether the specified class exists in the default classloader:
- Responsive WEB project: there is org.springframework.web.reactive.DispatcherHandler, and there are no org.springframework.web.servlet.DispatcherServlet and org.glassfish.jersey.servlet.ServletContainer
- Non WEB projects: org.springframework.web.context.ConfigurableWebApplicationContext and javax.servlet.Servlet do not exist
- SERVLET WEB project: neither of the above is satisfied
In this article, because the dependencies added in this article are:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
So the inference is: WebApplicationType.SERVLET
2.3. Spring factory loading mechanism
in order to interpret the following contents, we first introduce a very important concept: Spring Factory loading mechanism, and see how it is implemented and what it has done. The loading mechanism of the springfactoryesloader factory of SpringBoot is similar to the SPI mechanism provided by Java. It is a loading method provided by spring. Just create a new file META-INF/spring.factories in the classpath path, and fill in the interface and implementation classes in the format of Properties. You can instantiate the corresponding Bean through SpringFactoriesLoader. Where key can be the full name of an interface, annotation, or abstract class, and value is the corresponding implementation class. When there are multiple implementation classes, it is separated by commas.
2.3.1. Obtain Spring factory instance (important)
the specific path of the class where this method is located: org.springframework.boot.SpringApplication, which is also a common call entry for factory method calls (including boot registration initializer, container initializer and container listener to be analyzed).
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) { //The second parameter is an empty Class array, which is used to instantiate the default constructor return getSpringFactoriesInstances(type, new Class<?>[] {}); } 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 //Through the Spring factory loading mechanism, obtain the Set collection of implementation classes with the specified class name according to the class loader (the focus of the next section) Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); //Create the instantiated object of the above result List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); //The instantiated objects are sorted in ascending order according to order AnnotationAwareOrderComparator.sort(instances); //Returns the list of instantiated objects return instances; } @SuppressWarnings("unchecked") private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) { List<T> instances = new ArrayList<>(names.size()); // Traverses the fully qualified name of the instance object for (String name : names) { try { // Load the class through the class loader (reflection) Class<?> instanceClass = ClassUtils.forName(name, classLoader); // Assert whether it is the implementation class of the interface Assert.isAssignable(type, instanceClass); // Get the construction method by parameter type Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes); // Instantiate the class T instance = (T) BeanUtils.instantiateClass(constructor, args); // Add to instantiation result set instances.add(instance); } catch (Throwable ex) { throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex); } } // Return results return instances; }
the overall flow of the above code is:
- Get class loader
- Through the Spring factory loading mechanism, obtain the Set collection of implementation classes with the specified class name according to the class loader
- Traverse the implementation class name in the collection and load the class by reflection through the class loader
- Verify whether it is the implementation class of the specified class name (must be)
- Get the class construction method according to the parameter type. If the parameter type list is empty, it is the default construction method
- Instantiate the implementation class, including the default construction method, and add the results to the instantiated list
- Return instantiation result list
2.3.2,loadFactoryNames
the specific path of the class where this method is located: org.springframework.core.io.support.SpringFactoriesLoader
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) { // The class loader here is appClassLoader ClassLoader classLoaderToUse = classLoader; if (classLoaderToUse == null) { //If empty, get again classLoaderToUse = SpringFactoriesLoader.class.getClassLoader(); } // The fully qualified name of the class (that is, to resolve the key value required in the properties file) String factoryTypeName = factoryType.getName(); // According to the class loader, load the list of all class names under / META-INF/spring.factories under the classpath // From the result map < string, list < string > >, get the set list < string > of all implementation class names according to the specified type // The next section analyzes loadspring factories in detail return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList()); }
- Loadspring factories (classloader classloader) returns the parsed results of all META-INF/spring.factories files
- Get the result according to the specified class name. If not, an empty list will be returned
2.3.3. loadSpringFactories (core)
the specific path of the class where this method is located: org.springframework.core.io.support.SpringFactoriesLoader
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) { // Get the loaded result set from the cache according to the class loader Map<String, List<String>> result = cache.get(classLoader); if (result != null) { //If the result is not empty, the return result; } result = new HashMap<>(); try { // Constant facts_ RESOURCE_ The value of location is META-INF/spring.factories // Use the classloader to scan the files in all jars on the classpath: META-INF/spring.factories Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION); // What you get is a collection of specific paths of META-INF/spring.factories files, traversing the collection while (urls.hasMoreElements()) { // Get the specific path of each META-INF/spring.factories file URL url = urls.nextElement(); // Turn the META-INF/spring.factories file into a UrlResource object UrlResource resource = new UrlResource(url); // Use the property loading tool class to parse the file into Properties in the form of key and value // Where key is the specific class name and value is the class name of the implementation class separated by commas Properties properties = PropertiesLoaderUtils.loadProperties(resource); // Traverse Properties for (Map.Entry<?, ?> entry : properties.entrySet()) { // Gets the name of the qualified class name String factoryTypeName = ((String) entry.getKey()).trim(); // The value, that is, the configuration of the implementation class, is separated according to the "," symbol to obtain the string array of the class name of the implementation class String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String) entry.getValue()); // Traversal implementation class array for (String factoryImplementationName : factoryImplementationNames) { // Put the qualified class name key and the implementation class name value into the Map result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>()).add(factoryImplementationName.trim()); } } } // Replace all lists with unmodifiable lists containing unique elements // Replace all lists with a non modifiable list that contains unique elements result.replaceAll((factoryType, implementations) -> implementations.stream().distinct() .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList))); // Add the parsing results to the cache, and you don't have to load them again later cache.put(classLoader, result); } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } // Return results return result; }
overall loading process:
- Get the Spring factory loading result from the class loader cache. If you get it, you will return it directly
- If there is no data in the cache, initialize a Map to store the result set loaded by the Spring factory
- Use the classloader to scan the files in all jars on the classpath: META-INF/spring.factories to get a collection containing all spring.factories file paths
- Traverse the above collections to get the specific paths of META-INF/spring.factories files. Load the files through the property loading tool class to get Properties in the form of key and value
- After traversing Properties again, we get that key is the qualified class name, value is its implementation class, and multiple implementation classes are separated by commas
- Parse the value of value and add key and value to the previously created Map
- Load the parsed data of all META-INF/spring.factories files into the cache
- Return the parsed result
This version of Spring Boot 2.6.0 has got to 18 results in total:
2.4. Get the boot registration initializer
let's take a look at the specific path of the class in which the boot registration initializer is obtained in the construction method: org.springframework.boot.SpringApplication
//Get the implementation class of bootstrap registryinitializer from META-INF/spring.factories file //And use reflection to create an object and return it to the global variable bootstrap registryinitializers, List collection of spring application this.bootstrapRegistryInitializers = new ArrayList<>(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
according to the results obtained in the previous chapter, we did not find the boot registration initializer here.
2.5. Set container initializer
let's look at setting the container initializer again
//Get the implementation class of ApplicationContextInitializer from META-INF/spring.factories file (from cache) //The object is created by reflection and returned to the global variable initializers and List collection of spring application setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
the specific path of the class where this method is located: org.springframework.boot.SpringApplication
private List<ApplicationContextInitializer<?>> initializers; public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) { this.initializers = new ArrayList<>(initializers); }
We have talked about the Spring factory mechanism before. Here, we just call to get the results. In fact, there are 7 results obtained here:
where are these initializers? We can find the following configuration in the META-INF/spring.factories file of spring-boot-2.6.0.jar:
# Application Context Initializers 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 following configuration can be found in the META-INF/spring.factories file of spring-boot-autoconfigure-2.6.0.jar
# Initializers org.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\ org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
here, several initializers have been obtained and saved, waiting for subsequent operations.
2.6. Set container listener
the specific path of the class where this method is located: org.springframework.boot.SpringApplication
//Get the implementation class of ApplicationListener from META-INF/spring.factories file (from cache) //The object is created by reflection and returned to the global variable listeners and List collection of spring application setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
the specific path of the class where this method is located: org.springframework.boot.SpringApplication
private List<ApplicationListener<?>> listeners; public void setListeners(Collection<? extends ApplicationListener<?>> listeners) { this.listeners = new ArrayList<>(listeners); }
similarly, according to the previous Spring factory mechanism, we can get 8 listeners of the container from the cache:
similarly, we found the following configuration in the META-INF/spring.factories file of spring-boot-2.6.0.jar:
# 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.DelegatingApplicationListener,\ org.springframework.boot.context.logging.LoggingApplicationListener,\ org.springframework.boot.env.EnvironmentPostProcessorApplicationListener
the following configuration can be found in the META-INF/spring.factories file of spring-boot-autoconfigure-2.6.0.jar
# Application Listeners org.springframework.context.ApplicationListener=\ org.springframework.boot.autoconfigure.BackgroundPreinitializer
here, several container listeners have been obtained and saved, and they also need to wait for subsequent operations.
2.7. Infer the class of main method
the specific path of the class where this method is located: org.springframework.boot.SpringApplication
private Class<?> deduceMainApplicationClass() { try { // Get the StackTraceElement array, that is, the information of the stack StackTraceElement[] stackTrace = new RuntimeException().getStackTrace(); for (StackTraceElement stackTraceElement : stackTrace) { if ("main".equals(stackTraceElement.getMethodName())) { //stackTraceElement.getClassName(), get the definition class, that is, the class where the main method is located //The real main class is obtained by using reflection through the class name return Class.forName(stackTraceElement.getClassName()); } } } catch (ClassNotFoundException ex) { // Swallow and continue } return null; }
mainApplicationClass obtains the main startup class through the exception class stack. It can be seen here that the main startup class and the class corresponding to primarySources can be different.
epilogue
here, our spring application object has been created. In the next article, we will analyze the run method of the spring application object.