How are spring application objects built? SpringBoot source code

Note: the source code analysis corresponds to spring boot version 2.1.0.RELEASE

This connection What is the starting process of SpringBoot? Spring boot source code (7)

Learn from the past

Let's review the contents of the previous article. In the previous article, we analyzed the startup process of SpringBoot, and now we summarize the key steps:

  1. Build SpringApplication object to start SpringBoot;
  2. Load the eventpublishingronlistener object from the spring.factories configuration file to launch different life cycle events at different startup stages;
  3. Prepare environment variables, including system variables, environment variables, command line parameters and configuration files (such as application.properties), etc;
  4. Create the container ApplicationContext;
  5. Do some initialization work for the container object created in step 4, prepare some container property values, etc., and call the initialization methods of each ApplicationContextInitializer to execute some initialization logic, etc;
  6. Refresh the container, this step is crucial, is the focus of the focus, too many complex logic is implemented here;
  7. Call the run methods of ApplicationRunner and CommandLineRunner to implement the two interfaces to load some business data after the container is started;

In the spring boot process, each different startup stage will launch different built-in life cycle events, and then the corresponding listener will listen for these events to perform some initialization logic work, such as ConfigFileApplicationListener will listen for onApplicationEnvironmentPreparedEvent event to load environment variables.

2 Introduction

In the previous article, we saw that a new SpringApplication object was created to start the SpringBoot project. So, let's take a look at the construction process of spring application object today, and explain the SPI mechanism implemented by spring boot itself.

3. The construction process of spring application object

This section begins to explain the construction process of spring application objects, because the construction of an object is nothing more than assigning values to some of its member properties in its constructor, which rarely contains other additional business logic (of course, sometimes we may also open some threads in the constructor). Then, let's first look at some member properties that need to be used when constructing SpringApplication objects

// SpringApplication.java

/**
 * SpringBoot The starting class of is the main class containing the main function
 */
private Set<Class<?>> primarySources;
/**
 * Main class containing main function
 */
private Class<?> mainApplicationClass;
/**
 * Resource loader
 */
private ResourceLoader resourceLoader;
/**
 * Application type
 */
private WebApplicationType webApplicationType;
/**
 * Initializer
 */
private List<ApplicationContextInitializer<?>> initializers;
/**
 * Monitor
 */
private List<ApplicationListener<?>> listeners;

You can see that when building the SpringApplication object, the main task is to assign values to the six member properties in the above code. Now I'll take a look at the construction process of the SpringApplication object.

Let's go back to the code for building SpringApplication objects explained in the previous article:

// SpringApplication.java

// The run method is a static method for starting SpringBoot
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
		String[] args) {
	// Build a SpringApplication object and call its run method to start
	return new SpringApplication(primarySources).run(args);
}

Follow up in the constructor of SpringApplication:

// SpringApplication.java

public SpringApplication(Class<?>... primarySources) {
    // Continue to call another SpringApplication constructor
	this(null, primarySources);
}

Follow up on another SpringApplication constructor:

// SpringApplication.java

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
	// [1] Assign a value to the resourceLoader property. Note that the passed resourceLoader parameter is null
	this.resourceLoader = resourceLoader;
	Assert.notNull(primarySources, "PrimarySources must not be null");
	// [2] Assign a value to the primarySources property. The passed primarySources is actually the main application.class in springapplication.run (main application. Class, args)
	this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
	// [3] Assign a value to the webApplicationType attribute, and determine the application type according to the type of class in the classpath
	this.webApplicationType = WebApplicationType.deduceFromClasspath();
	// [4] Assign values to the initializers property, load the implementation class of the ApplicationContextInitializer interface from spring.factories using the SPI customized by SpringBoot, and assign values to the initializers property
	setInitializers((Collection) getSpringFactoriesInstances(
			ApplicationContextInitializer.class));
	// [5] Assign a value to the listeners property, load the implementation class of the ApplicationListener interface from spring.factories using the SPI customized by SpringBoot, and assign a value to the listeners property
	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
	// [6] Assign a value to the mainApplicationClass attribute, that is, to infer which class called the main function, and then assign a value to the mainApplicationClass attribute, which is used to print some logs in the subsequent startup process.
	this.mainApplicationClass = deduceMainApplicationClass();
}

It can be seen that when building the SpringApplication object, it is just to assign the member properties of the six aforementioned SpringApplication classes, and do some initialization work:

  1. Assign a value to the resourceLoader property, the resourceLoader property and the resource loader. At this time, the passed resourceLoader parameter is null;
  2. Assign a value to the primarySources attribute, which is SpringApplication.run(MainApplication.class,args); the main application.class passed in is the startup class of the SpringBoot project, which is mainly used to scan the Configuration class to load bean s;
  3. Assign value to webApplicationType attribute, webApplicationType attribute, representing Application type, and judge according to the corresponding Application class existing in classpath. Because we need to determine which Environment object to create and which ApplicationContext to create according to webApplicationType later, please refer to the following Section 3.1 for detailed analysis;
  4. Assign a value to the initializers attribute. The initializers attribute is the list < applicationcontextinitializer <? > > collection. Use the SPI mechanism of SpringBoot to load from the spring.factories configuration file. Later, when initializing the container, these initializers will be used to perform some initialization work. Because the SPI mechanism implemented by SpringBoot itself is quite important, it is analyzed in a separate section. For detailed analysis, please refer to the following section 4;
  5. Assign a value to the listeners attribute, which is the list < applicationlistener <? > > collection. Also, use the SPI mechanism of spring boot to load from the spring.factories configuration file. Because some events will be launched at different stages during the spring boot process, these loaded listeners are here to monitor some life cycle events during the spring boot process;
  6. Assign a value to the mainApplicationClass attribute. The mainApplicationClass attribute indicates the class containing the main function. That is to say, which class calls the main function here, and then assign the fully qualified name of this class to the mainApplicationClass attribute, which is used to print some logs in the subsequent startup process. For detailed analysis, see the following section 3.2.

3.1 inferred project application type

Next, we analyze step [3] of constructing SpringApplication object WebApplicationType.deduceFromClasspath(); this Code:

// WebApplicationType.java

public enum WebApplicationType {
        // Common applications
	NONE,
	// Servlet type web application
	SERVLET,
	// web application of Reactive type
	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 "org.springframework." + "web.servlet.DispatcherServlet" and "org.glassfish.jersey.servlet.ServletContainer" do not exist in the classpath
		// WebApplicationType.REACTIVE is returned, indicating that it is a 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.servlet.Servlet",
		//       "org.springframework.web.context.ConfigurableWebApplicationContext" }
		// If none of them exist in classpath, it indicates whether they are web applications
		for (String className : SERVLET_INDICATOR_CLASSES) {
			if (!ClassUtils.isPresent(className, null)) {
				return WebApplicationType.NONE;
			}
		}
		// Finally return to normal web application
		return WebApplicationType.SERVLET;
	}
}

According to the above code, judge the application type according to the classpath, that is, judge whether the specified flag class exists or not through the reflection loading classpath to judge whether it is a Reactive application, a Servlet type web application or an ordinary application.

3.2 infer which class called the main function

Let's skip steps [4] and [5] of constructing SpringApplication object, and first analyze step [6] of constructing SpringApplication object this.mainApplicationClass = deduceMainApplicationClass(); this Code:

// SpringApplication.java

private Class<?> deduceMainApplicationClass() {
	try {
		// Get the StackTraceElement object array stackTrace. The StackTraceElement object stores call stack related information (such as class name, method name, etc.)
		StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
		// Traversal stackTrace array
		for (StackTraceElement stackTraceElement : stackTrace) {
			// If the call method name of the stackTraceElement record is equal to main
			if ("main".equals(stackTraceElement.getMethodName())) {
				// Then return the class name of the stackTraceElement record, that is, the class name containing the main function
				return Class.forName(stackTraceElement.getClassName());
			}
		}
	}
	catch (ClassNotFoundException ex) {
		// Swallow and continue
	}
	return null;
}

It can be seen that the main function of the deduceMainApplicationClass method is to get which class called the main method from the StackTraceElement call stack array, and then return the assignment to the mainApplicationClass property, which is then used to print some logs in the subsequent startup process.

4. Interpretation of SPI mechanism of springboot

Since the SPI mechanism of spring boot is a very important knowledge point, it is analyzed in a separate section here. As we all know, spring boot does not use Java's SPI mechanism (see the author's How does Java implement its SPI mechanism? , it's really full of dry goods), but a set of self defined SPI mechanism is implemented. Spring boot can load initializer implementation classes, listener implementation classes, auto configuration classes and so on by using SPI mechanism of custom implementation. If we want to add auto configuration classes or custom listeners, an important step is to configure them in spring.factories before they are loaded by spring boot.

OK, let's focus on how spring boot implements its own SPI mechanism.

This is followed by the code in step [4] and step [5] of constructing spring application object in Section 3. Because step [4] and step [5] use the SPI mechanism of spring boot to load extension implementation classes, only the setinitializers ((Collection) getspringfactorysinstances (ApplicationContextInitializer. Class) in step [4] are analyzed here; In this code, let's see how SpringBoot implements its own set of SPI in getSpringFactoriesInstances method to load the extension implementation class of ApplicationContextInitializer initializer interface?

// SpringApplication.java

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
    // Continue to call the overloaded getSpringFactoriesInstances method for loading
    return getSpringFactoriesInstances(type, new Class<?>[] {});
}

Continue to follow up the overloaded getSpringFactoriesInstances method:

// SpringApplication.java

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
		Class<?>[] parameterTypes, Object... args) {
	// [1] Get class loader
	ClassLoader classLoader = getClassLoader();
	// Use names and ensure unique to protect against duplicates
	// [2] The interface type and class loader are passed into the loadFactoryNames method as parameters, and the interface implementation classes are loaded from the spring.factories configuration file
	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] Sort
	AnnotationAwareOrderComparator.sort(instances);
	// [5] Return the loaded and instantiated interface implementation class
	return instances;
}

As you can see, the most important step in the SPI mechanism code of the spring boot custom implementation is step [1], [2], [3] of the above code, which will be analyzed in detail below.

4.1 get class loader

Remember How does Java implement its SPI mechanism? In this article, Java's SPI mechanism uses thread context class loader to load extension classes by default. What kind of loader does spring boot implement to load extension implementation classes in the spring.factories configuration file?

Let's take a look at the ClassLoader classLoader = getClassLoader() in step [1]; see the code first:

// SpringApplication.java

public ClassLoader getClassLoader() {
	// When constructing spring application object, the resourceLoader parameter passed in is null, so the logic in if statement will not be executed
	if (this.resourceLoader != null) {
		return this.resourceLoader.getClassLoader();
	}
	// Get the default classloader
	return ClassUtils.getDefaultClassLoader();
}

Continue to follow up the getDefaultClassLoader method:

// ClassUtils.java

public static ClassLoader getDefaultClassLoader() {
	ClassLoader cl = null;
	try {
	        // [key] get thread context class loader
		cl = Thread.currentThread().getContextClassLoader();
	}
	catch (Throwable ex) {
		// Cannot access thread context ClassLoader - falling back...
	}
	// The logic here doesn't work
	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...
			}
		}
	}
	// Return to the thread context class loader just obtained
	return cl;
}

You can see that the original SPI mechanism of spring boot also uses the thread context class loader to load the extension implementation class in the spring.factories file!

4.2 loading the SPI extension class in the spring.factories configuration file

Let's take a look at the spring factorysloader.loadfactorynames (type, classloader) in step [2] next. How does the code load the SPI extension class in the spring.factories configuration file?

// SpringFactoriesLoader.java

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
        // factoryClass is the SPI interface, such as ApplicationContextInitializer,EnableAutoConfiguration and other interfaces
	String factoryClassName = factoryClass.getName();
	// [main line, focus] continue to call the loadspringfactors method to load the SPI extension class
	return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

Follow up the loadspringfactors method:

// SpringFactoriesLoader.java

/**
 * The location to look for factories.
 * <p>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) {
	// Take classLoader as the key to get from the cache first, and return directly if it can
	MultiValueMap<String, String> result = cache.get(classLoader);
	if (result != null) {
		return result;
	}
	// If there is no record in the cache, go to the spring.factories configuration file to get
	try {
		// Here, load the url path of "MATF-INF/spring.factories" file in all jar packages
		Enumeration<URL> urls = (classLoader != null ?
				classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
				ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
		result = new LinkedMultiValueMap<>();
		// Traverse the urls path, and set the key value pairs of all spring.factories files (key:SPI interface class name value:SPI extension class name)
		// Load into result collection
		while (urls.hasMoreElements()) {
			// Take out a url
			URL url = urls.nextElement();
			// Encapsulate the url into the UrlResource object
			UrlResource resource = new UrlResource(url);
			// Use the loadProperties method of PropertiesLoaderUtils to load the spring.factors file key value pair into the Properties object
			Properties properties = PropertiesLoaderUtils.loadProperties(resource);
			// Traverse the newly loaded key value pair properties object
			for (Map.Entry<?, ?> entry : properties.entrySet()) {
				// Take out the SPI interface name
				String factoryClassName = ((String) entry.getKey()).trim();
				// The implementation class corresponding to the traversal SPI interface name is the SPI extension class
				for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
					// SPI interface name as key, SPI extension class as value into result
					result.add(factoryClassName, factoryName.trim());
				}
			}
		}
		// Put classLoader as key and result as value into cache
		cache.put(classLoader, result);
		// Finally, the result object is returned
		return result;
	}
	catch (IOException ex) {
		throw new IllegalArgumentException("Unable to load factories from location [" +
				FACTORIES_RESOURCE_LOCATION + "]", ex);
	}
}

As shown in the above code, the main task of the loadspringfactors method is to use the thread context class loader obtained before to load all the extension implementation classes of all the SPI interfaces in the spring.factors configuration file in the classpath, and then put them into the cache. Note that all the SPI extension implementation classes are loaded at one time, so you can get the SPI extension classes directly from the cache according to the SPI interface. You don't need to get the extension implementation classes corresponding to the SPI interface from the spring.factories configuration file again. For example, the extension implementation classes of the interfaces of ApplicationListener,FailureAnalyzer and EnableAutoConfiguration can be obtained directly from the cache.

Thinking 1: why do you want to get all the extension classes from the spring. Factories configuration file and put them into the cache at one time? Instead of getting it from the spring.factories configuration file according to the SPI interface every time?

Think 2: remember the function of AutoConfigurationImportFilter interface mentioned in spring boot's auto configuration source code? Now we should be able to understand the function of this interface more clearly.

After all SPI extension implementation classes are loaded, getordefault (factoryClassName, The collections. Emptylist()) method filters the current corresponding extension implementation classes according to the SPI interface name. For example, the factoryClassName parameter passed in here is called the ApplicationContextInitializer interface, which will take the SPI extension implementation classes corresponding to the ApplicationContextInitializer interface from the cached data as key s. All SPI extension implementation classes corresponding to the ApplicationContextInitializer interface obtained from spring.factories are shown in the following figure:

4.3 instantiate the SPI extension class loaded from spring.factories

After getting all SPI extension implementation classes corresponding to ApplicationContextInitializer interface from spring.factories, these SPI extension classes will be instantiated.

Now let's look at the instantiation code in step [3] above: List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);.

// SpringApplication.java

private <T> List<T> createSpringFactoriesInstances(Class<T> type,
		Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
		Set<String> names) {
	// Create a new instances collection to store the SPI extension class objects that will be instantiated later
	List<T> instances = new ArrayList<>(names.size());
	// Traverse the name set, which stores the fully qualified names of all SPI extension classes
	for (String name : names) {
		try {
			// Using reflection to load classes based on fully qualified names
			Class<?> instanceClass = ClassUtils.forName(name, classLoader);
			// Assert whether the SPI extension class just loaded belongs to SPI interface type
			Assert.isAssignable(type, instanceClass);
			// Get the constructor of the SPI extension class
			Constructor<?> constructor = instanceClass
					.getDeclaredConstructor(parameterTypes);
			// Instantiate SPI extension class
			T instance = (T) BeanUtils.instantiateClass(constructor, args);
			// Add to instances collection
			instances.add(instance);
		}
		catch (Throwable ex) {
			throw new IllegalArgumentException(
					"Cannot instantiate " + type + " : " + name, ex);
		}
	}
	// Return
	return instances;
}

The above code is very simple. The main thing to do is instantiate the SPI extension class. Now, the SPI mechanism of spring boot customization has been analyzed.

Think 3: why does SpringBoot abandon Java's SPI and customize a set of SPI?

5 Summary

Well, this is the end of the film. First, summarize the previous knowledge points:

  1. The construction process of spring application object is analyzed;
  2. This paper analyzes a set of SPI mechanism implemented by SpringBoot itself.

Feel what you feel

Since I started to write the source code analysis article in February, I have also known some technical bulls. From them, I can see that the more powerful people are, the harder they work. In retrospect, my knowledge is also very narrow. What's more, I don't have a deep understanding of the technology I'm involved in. In a word, it's still very delicious. When I see that the big bulls who are more powerful than myself are still fighting so hard, why don't I work hard? I like Mr. Dingwei's saying: "only perseverance". Then step by step, I believe that I can make greater progress, continue to refuel.

Praise and forwarding is the greatest incentive for the author!

Due to the limited level of the author, if there are any mistakes in the article, please point out, thank you.

Official account [source notes], focusing on the analysis of the source code of the Java backend framework.

Tags: Programming Spring Java SpringBoot Attribute

Posted on Mon, 06 Apr 2020 04:56:42 -0700 by isam4m