Github source code Analysis project launched!! Here is the Github address of the note:
Github.com/yuanmabiji/…
What is the startup process of SpringBoot? SpringBoot source code (seven)
1 Review the old and learn the new
Review the past, let’s briefly review the content of the last article, the last article we analyzed the startup process of SpringBoot, now the key steps are condensed and summarized:
- build
SpringApplication
Object to launch SpringBoot; - from
spring.factories
Load in configuration fileEventPublishingRunListener
Object is used to emit different life cycle events at different startup phases; - Prepare environment variables, including system variables, environment variables, command line parameters, and configuration files (e.g
application.properties
), etc. - Create a container
ApplicationContext
; - Do some initialization for the container object created in step 4, prepare some container property values, etc., and call each
ApplicationContextInitializer
To perform some initialization logic, etc. - Refresh the container, this is the most important step, the most important step, too much complex logic is implemented here;
- call
ApplicationRunner
andCommandLineRunner
The run method can realize the two interfaces in the container started after loading some business data;
During SpringBoot startup, each startup phase emits a different built-in lifecycle event, And then the corresponding listener will listen to these events to perform some initialization logic work such as ConfigFileApplicationListener will monitor onApplicationEnvironmentPreparedEvent incident to load the environment variables, etc.
2 the introduction
In the previous article, we saw that a New SpringApplication object was created to start the SpringBoot project. So, today we will look at the process of building SpringApplication objects and explain the SPI mechanism implemented by SpringBoot itself.
3 SpringApplication object building process
This section starts with the construction of SpringApplication objects, because the construction of an object is nothing more than assigning values to its member properties in its constructor, and contains little additional business logic (although sometimes we may open threads in the constructor). So, let’s look at some of the member properties used to construct SpringApplication objects:
// SpringApplication.java
/** * The boot class of SpringBoot is the main class that contains the main function */
privateSet<Class<? >> primarySources;/** * the main class containing the main function */
privateClass<? > mainApplicationClass;/** * resource loader */
private ResourceLoader resourceLoader;
/** * Application type */
private WebApplicationType webApplicationType;
/** * initializer */
privateList<ApplicationContextInitializer<? >> initializers;/** * listener */
privateList<ApplicationListener<? >> listeners;Copy the code
As you can see, the SpringApplication object is built primarily by assigning values to the six member properties in the code above. Now I’ll move on to the construction of the SpringApplication object.
Let’s go back to the code that builds the SpringApplication object in the previous article:
// SpringApplication.java
// The run method is a static method used to start SpringBoot
public static ConfigurableApplicationContext run(Class
[] primarySources, String[] args) {
// Build a SpringApplication object and call its run method to launch it
return new SpringApplication(primarySources).run(args);
}
Copy the code
Follow up in the SpringApplication constructor:
// SpringApplication.java
public SpringApplication(Class
... primarySources) {
// Continue to call another SpringApplication constructor
this(null, primarySources);
}
Copy the code
Follow up with another SpringApplication constructor:
// SpringApplication.java
public SpringApplication(ResourceLoader resourceLoader, Class
... primarySources) {
// [1] Assign the resourceLoader attribute. Note that the resourceLoader parameter passed is null
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
PrimarySources = springApplication. run(mainApplication. class, args); The MainApplication. Class
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// [3] Assign the webApplicationType attribute to determine the application type based on what type of class exists in the classpath
this.webApplicationType = WebApplicationType.deduceFromClasspath();
/ / [4] to initializers attribute assignment, use the SPI SpringBoot custom, since the spring. The factories in loading ApplicationContextInitializer interface implementation class and assign a value to the initializers properties
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
[5] Load the ApplicationListener interface implementation class from Spring. Factories using the SpringBoot custom SPI and assign values to the Listeners
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// [6] Assign the mainApplicationClass attribute, i.e. infer which class called main, and then assign the mainApplicationClass attribute, which will be used to print some logs later in the startup process.
this.mainApplicationClass = deduceMainApplicationClass();
}
Copy the code
As you can see, building the SpringApplication object is actually doing some initialization work by assigning the member properties of the previous six SpringApplication classes:
- to
resourceLoader
Attribute assignment.resourceLoader
Property, the resource loader, passed in at this timeresourceLoader
Parameters fornull
; - to
primarySources
Attribute assignment.primarySources
attributeNamely SpringApplication. Run (MainApplication. Class, args);
In the incomingMainApplication.class
This class is the startup class of the SpringBoot project and is used for scanningConfiguration
Class loadingbean
; - to
webApplicationType
Attribute assignment.webApplicationType
Property representing the application type, according toclasspath
Existential correspondenceApplication
Class to judge. Because it’s based onwebApplicationType
To determine which one to createEnvironment
Object and create whichApplicationContext
For detailed analysis, please see the followingIn section 3.1
; - to
initializers
Attribute assignment.initializers
Properties forList<ApplicationContextInitializer<? >>
Collection, using SpringBoot’s SPI mechanism fromspring.factories
These initializers are later applied to perform some initialization work when the container is initialized. The SPI mechanism implemented by SpringBoot itself is important, so it is analyzed in a separate section. For detailed analysis, see the following sectionThe fourth section
; - to
listeners
Attribute assignment.listeners
Properties forList<ApplicationListener<? >>
Collection, also utilizing SpringBoot’s SPI mechanism fromspring.factories
Load in configuration file. Since SpringBoot launches events at different stages, these loaded listeners are used to listen for lifecycle events during SpringBoot launches. - to
mainApplicationClass
Attribute assignment.mainApplicationClass
Property means containmain
The class of the function, that is, which class is being calledmain
Function, and then assigns the fully qualified name of the class tomainApplicationClass
Property to print some logs later in the startup process, as detailed belowIn section 3.2
.
3.1 Infer the project application type
We then analyze tectonic SpringApplication object step [3] WebApplicationType. DeduceFromClasspath (); This code:
// WebApplicationType.java
public enum WebApplicationType {
// Common applications
NONE,
// Servlet type Web application
SERVLET,
// Reactive Web applications
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(a) {
"Org.springframework." + does not exist in classpath "Web. Servlet. DispatcherServlet" and "org. Anyone. Jersey. Servlet. ServletContainer"
/ / return WebApplicationType. REACTIVE, it shows that is REACTIVE application
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
If / / {" javax.mail. Servlet. Servlet ",
// "org.springframework.web.context.ConfigurableWebApplicationContext" }
// None of them exist in the classpath
for (String className : SERVLET_INDICATOR_CLASSES) {
if(! ClassUtils.isPresent(className,null)) {
returnWebApplicationType.NONE; }}// Finally return to the normal Web application
returnWebApplicationType.SERVLET; }}Copy the code
As shown in the above code, the application type is determined according to the classpath. That is, the specified flag class is determined by reflection loading of the classpath to determine whether the application is Reactive, Servlet type Web application or common application.
3.2 Infer which class calls main
Let’s skip steps [4] and [5] for constructing the SpringApplication object, First brought to the analysis of tectonic SpringApplication object step [6] this. MainApplicationClass = deduceMainApplicationClass (); This code:
// SpringApplication.java
privateClass<? > deduceMainApplicationClass() {try {
The StackTraceElement object stores information about the call stack (class name, method name, etc.).
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
// Iterate over the stackTrace array
for (StackTraceElement stackTraceElement : stackTrace) {
// If the stackTraceElement record calls a method name equal to main
if ("main".equals(stackTraceElement.getMethodName())) {
// Return the class name of the stackTraceElement record containing the main function
returnClass.forName(stackTraceElement.getClassName()); }}}catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
Copy the code
Can see from StackTraceElement deduceMainApplicationClass method is the main purpose of the call stack in the array for which class calls the main method, and then return to the assigned to mainApplicationClass properties, It is then used to print some logs in the later startup process.
4 Interpretation of SPI mechanism of SpringBoot
Since SpringBoot’s SPI mechanism is an important point of knowledge, this section will analyze it separately. As we all know, SpringBoot does not use Java’s SPI mechanism. , is really full of dry goods), but a custom implementation of their own SPI mechanism. SpringBoot utilizes the SPI mechanism of a custom implementation to load initializer implementation classes, listener implementation classes, auto-configuration classes, and so on. If we’re adding auto-configuration classes or custom listeners, it’s important to configure them in Spring.Factories before SpringBoot loads them.
Ok, so let’s focus on how SpringBoot implements its own SPI mechanism.
Here is the code for step [4] and Step [5] of section 3 to construct the SpringApplication object, because step [4] and step [5] both use SpringBoot’s SPI mechanism to load the extension implementation class. So here only analysis step [4] setInitializers ((Collection) getSpringFactoriesInstances (ApplicationContextInitializer. Class)); This code, see SpringBoot getSpringFactoriesInstances method is how to realize its own set of SPI to load ApplicationContextInitializer initializer extension of the interface implementation class?
// SpringApplication.java
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
/ / continue calling overloaded getSpringFactoriesInstances methods for loading
return getSpringFactoriesInstances(type, newClass<? > [] {}); }Copy the code
Continue to follow up overloading getSpringFactoriesInstances method:
// SpringApplication.java
private <T> Collection<T> getSpringFactoriesInstances(Class
type, Class
[] parameterTypes, Object... args)
{
[1] get the classloader
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
// [2] Load the interface implementation classes from the Spring. factories configuration file by passing the interface type and classloader as parameters to the loadFactoryNames method
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// [3] instantiate the interface implementation class loaded from Spring.Factories
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
// [4]
AnnotationAwareOrderComparator.sort(instances);
// [5] Returns the loaded and instantiated interface implementation class
return instances;
}
Copy the code
It can be seen that the most important steps [1], [2] and [3] in the SPI mechanism code of SpringBoot custom implementation are the above steps, which will be analyzed in the following sections.
4.1 Getting a class loader
Remember how Java implemented its own SPI mechanism? In this article, Java’s SPI mechanism uses a thread-context classloader to load extension classes by default. SpringBoot’s own SPI mechanism uses what class loader to load extension classes in the Spring.Factories configuration file?
ClassLoader = getClassLoader(); Here’s a sneak peek at the code:
// SpringApplication.java
public ClassLoader getClassLoader(a) {
// We passed the null resourceLoader parameter to the SpringApplicaiton object, so the logic in the if statement will not be executed
if (this.resourceLoader ! =null) {
return this.resourceLoader.getClassLoader();
}
// Get the default class loader
return ClassUtils.getDefaultClassLoader();
}
Copy the code
Follow up with the getDefaultClassLoader method:
// ClassUtils.java
public static ClassLoader getDefaultClassLoader(a) {
ClassLoader cl = null;
try {
// Get the thread context class loader
cl = Thread.currentThread().getContextClassLoader();
}
catch (Throwable ex) {
// Cannot access thread context ClassLoader - falling back...
}
// The logic here will not be executed
if (cl == null) {
// No thread context class loader -> use class loader of this class.
cl = ClassUtils.class.getClassLoader();
if (cl == null) {
// getClassLoader() returning null indicates the bootstrap ClassLoader
try {
cl = ClassLoader.getSystemClassLoader();
}
catch (Throwable ex) {
// Cannot access system ClassLoader - oh well, maybe the caller can live with null...}}}// Returns the thread context classloader you just got
return cl;
}
Copy the code
SpringBoot uses the thread context classloader to load the extended implementation classes in the Spring. factories file.
4.2 Loading the SPI extension classes in the Spring. factories configuration file
Let’s look at the first step (2) the SpringFactoriesLoader. LoadFactoryNames (type, this). This code is how to load the spring factories SPI in the configuration file extension classes?
// SpringFactoriesLoader.java
public static List<String> loadFactoryNames(Class<? > factoryClass,@Nullable ClassLoader classLoader) {
/ / factoryClass SPI interface, such as ApplicationContextInitializer EnableAutoConfiguration interface, etc
String factoryClassName = factoryClass.getName();
// Continue calling the loadSpringFactories method to load the SPI extension class
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
Copy the code
Continue with the loadSpringFactories method:
// SpringFactoriesLoader.java
/** * The location to look for factories. * Can be present in multiple JAR files. */
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
// Use classLoader as the key to retrieve the key from the cache
MultiValueMap<String, String> result = cache.get(classLoader);
if(result ! =null) {
return result;
}
// If there are no records in the cache, go to the spring.factories configuration file to obtain them
try {
// Load the URL paths of all jar packages that contain the "matf-INF /spring.factories" filesEnumeration<URL> urls = (classLoader ! =null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
// Traverse the urls path and add the key/value pairs of all spring.factories (key:SPI value:SPI extension)
// load into the result collection
while (urls.hasMoreElements()) {
// Retrieve a URL
URL url = urls.nextElement();
// Encapsulate the URL into an UrlResource object
UrlResource resource = new UrlResource(url);
// Load the spring.factories file key-value pair contents into the Properties object using the loadProperties method of PropertiesLoaderUtils
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
// Iterate over the newly loaded key-value pair Properties object
for(Map.Entry<? ,? > entry : properties.entrySet()) {// Fetch the SPI interface name
String factoryClassName = ((String) entry.getKey()).trim();
// Iterate over the implementation class corresponding to the SPI interface name, namely the SPI extension class
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
// SPI interface name as key, SPI extension class as value in resultresult.add(factoryClassName, factoryName.trim()); }}}// Use the classLoader as the key and result as the value to cache
cache.put(classLoader, result);
// Finally returns the result object
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex); }}Copy the code
The main thing the loadSpringFactories method does is load all the extended implementation classes for all SPI interfaces from all the Spring. factories configuration files in the classpath using the thread context classloaders that we previously obtained and put them into the cache. The spring-factories configuration file does not need to be loaded with the SPI extension implementation classes. The spring-factories configuration file does not need to be loaded with the SPI extension implementation classes. Such as after obtaining ApplicationListener FailureAnalyzer and EnableAutoConfiguration the extension of the interface implementation class can be directly obtained from the cache.
Thought 1: Why grab all the extended classes from the Spring. factories configuration file and put them in the cache at once? Instead of going to the Spring. factories configuration file every time according to the SPI interface?
Before thinking about 2: remember when I speak SpringBoot automatic configuration of the source code for mentioned AutoConfigurationImportFilter this interface? Now we should have a better idea of what this interface does.
After loading all SPI extension implementation classes, call getOrDefault(factoryClassName, collections.emptyList ()) to filter the current corresponding extension implementation class based on the SPI interface name. Such as incoming factoryClassName parameter called ApplicationContextInitializer interface here, So the interface will be the key from the cache the data just now ApplicationContextInitializer corresponding SPI interface extension implementation class. ApplicationContextInitializer retrieved from spring. Factories all the SPI interface corresponding extension implementation such as shown below:
4.3 Instantiate the SPI extension classes loaded from Spring.Factories
From the spring. The factories in get to ApplicationContextInitializer all SPI interface corresponding extension after implementation class, at this point to expand the SPI class to instantiate.
Now let’s look at the instantiation code of step [3] above: List
instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); .
// SpringApplication.java
private <T> List<T> createSpringFactoriesInstances(Class
type, Class
[] parameterTypes, ClassLoader classLoader, Object[] args, Set
names)
{
// Create a new Instances collection to store SPI extended class objects that will be instantiated later
List<T> instances = new ArrayList<>(names.size());
// Iterate through the name collection, which stores the fully qualified names of all SPI extension classes
for (String name : names) {
try {
// Use reflection to load classes based on fully qualified namesClass<? > instanceClass = ClassUtils.forName(name, classLoader);// Assert whether the SPI extension class you just loaded is an SPI interface type
Assert.isAssignable(type, instanceClass);
// Get the constructor for the SPI extension classConstructor<? > constructor = instanceClass .getDeclaredConstructor(parameterTypes);// Instantiate the SPI extension class
T instance = (T) BeanUtils.instantiateClass(constructor, args);
// add instances to instances collection
instances.add(instance);
}
catch (Throwable ex) {
throw new IllegalArgumentException(
"Cannot instantiate " + type + ":"+ name, ex); }}/ / return
return instances;
}
Copy the code
The above code is very simple, and the main thing to do is to instantiate the SPI extension class. Well, SpringBoot’s custom SPI mechanism has been analyzed.
Thought 3: Why did SpringBoot abandon Java’s SPI and create a custom ONE?
5 subtotal
Well, that’s the end of this piece, and then summarize the previous knowledge points:
- Analysis of the
SpringApplication
Object construction process; - An SPI mechanism implemented by SpringBoot itself is analyzed.
6. Express your feelings
Since I began to write source code analysis articles in February, I have also known some technical masters, from them to see that the more powerful people work harder. In retrospect, I now have a very narrow range of knowledge, more importantly, I have no depth of technology involved, in a word, but also very vegetables, and see more powerful than their own big cattle are still so hard, I have no reason not to work hard? Like ding Wei teacher’s words: “only perseverance”. Then take one step at a time, believe that you can make greater progress, continue to work.