Writing in the front

Because I have also read some corresponding Spring related source code reading videos and articles, but still have a little understanding of this part, and lazy, so to record the way to urge their overall reading down, there will be a lot of omissions, please forgive me.

My ultimate goal is to cover every line of code in all startup processes with comments. If you find any redundancy, please point it out in the comments.

The overall reading

Since this is the first time for me to read the relevant Spring source code, there may be a rough reading section, which will also be recorded accordingly. If you have the general context, skip the whole reading section and start right away.

Environment set up

Start with the Boot class first, we first build a simple Spring Boot environment, this time read the source version is 2.6.0-SNAPSHOT.


      
<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 the SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>Server - the 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 the 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 the code

Start class

Then start our AppRun, I believe many people are from @SpringBootApplication start, indeed if the interview, then you only need to explain this part of the content can kill most of the small companies, but we are fully source code parsing, so our focus is still on the following aspects.

  • How do you get it up and running
  • Once it’s running, how do you get the bean configuration

This is the simplest purpose of our cursory overall reading.

First of all, let’s cut out some important parts.

  • SpringApplication (Class initialization)
  • Springapplication.run (method call)

Start the class

org.springframework.boot.SpringApplication

First locate the constructor and see what it has in store 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, it is asserted that an IllegalArgumentException will be thrown
		Assert.notNull(primarySources, "PrimarySources must not be null");
		// Primary resources are de-duplicated and placed in primary resources
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		// Deduce the classpath from the path, and finally get the result "SERVLET"
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
		// Get Bootstrap Registry Initializers from the Spring factory
		this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
		// Get the Spring factory instance -> application context initializer
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		// Get the Spring factory instance -> application listener
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		// Derive the main application class
		this.mainApplicationClass = deduceMainApplicationClass();
	}
Copy the code

After a brief reading, we came to a conclusion that this part is the acquisition and setting of some basic types of information, similar to the function of environment preparation, so we ended up with several resources.

  • List of major resources
  • Web App type Servlet
  • Application context initializer
    • org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
  • Application listeners
    • 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 com.zhisan.apprun

The loaded parts of the application listener are the ones we care about because we don’t yet know how they are loaded. See the loading Spring Factory section.

Start class start method

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

After the SpringApplication is initialized, it is ready to run its run method, and most of the overall run logic is here, which we will focus on in this section.

public ConfigurableApplicationContext run(String... args) {
	// Start a simple stopwatch
	// tip: used for timing and can be ignored
	StopWatch stopWatch = new StopWatch();
	stopWatch.start();
	// Create boot context
	DefaultBootstrapContext bootstrapContext = createBootstrapContext();
	// Define configurable application context variables
	ConfigurableApplicationContext context = null;
	// Configure the headless attribute
	configureHeadlessProperty();
	// Get the run 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);
		// The configuration ignores Bean information
		configureIgnoreBeanInfo(environment);
		// Print the banner Spring icon (customizable)
		Banner printedBanner = printBanner(environment);
		// Create application context
		context = createApplicationContext();
		// Set the application to start
		context.setApplicationStartup(this.applicationStartup);
		// Prepare the context
		prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
		// Refresh the context
		// This involves starting Spring
		// Start the Web service
		refreshContext(context);
		// Refresh and execute
		// tip: not implemented for extensibility
		afterRefresh(context, applicationArguments);
		// The clock ends
		stopWatch.stop();
		// Time to print
		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 the code

Read line by line

Infer Web types from the classpath

org.springframework.boot.WebApplicationType#deduceFromClasspath

static WebApplicationType deduceFromClasspath(a) {
	/ / judgment org. Springframework. Web. Reactive. DispatcherHandler exist, And org. Springframework. Web. Servlet. DispatcherServlet and org. Anyone. Jersey. Servlet. The ServletContainer does not exist
	// Reactive
	if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
			&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
		return WebApplicationType.REACTIVE;
	}
	Javax.mail. Servlet. / / there "servlet" and "org. Springframework. Web. Context. ConfigurableWebApplicationContext" one of them returns NONE
	for (String className : SERVLET_INDICATOR_CLASSES) {
		if(! ClassUtils.isPresent(className,null)) {
			returnWebApplicationType.NONE; }}// If neither match, the default Servlet is used
	return WebApplicationType.SERVLET;
}
Copy the code

Get Bootstrap Registry Initializers from the Spring factory

org.springframework.boot.SpringApplication#getBootstrapRegistryInitializersFromSpringFactories

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

Loading the Spring Factory

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

Generic factory loading mechanism for internal use within the framework.

The SpringFactoriesLoader loads and instantiates factories of a given type from “meta-INF /spring.factories” files that may exist in multiple JAR files in the classpath. The spring.Factories file must be in the 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. Such as: Example. MyService = example. MyServiceImpl1, example MyServiceImpl2 example of them. The MyService is the interface name, MyServiceImpl1 and MyServiceImpl2 are two implementations.

Above is org. Springframework. Core. IO. Support. SpringFactoriesLoader class introduction, if finish see this part of the introduction, has already begun, so this is a list of the specific work in this class I a line-by-line parsing method.

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);
	// Return the contents of the class loader if it exists
	if(result ! =null) {
		return result;
	}

	// Initializes the contents of the class loader if it does not exist
	result = new HashMap<>();
	try {
		// Get the resource -> meta-INF/spring.Factories list
		// Multiple meta-INF /spring.factories may exist
		Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
		// loop loading
		while (urls.hasMoreElements()) {
			// Get the URL of the meta-INF /spring.factories file
			URL url = urls.nextElement();
			// Load resources
			UrlResource resource = new UrlResource(url);
			// Load resource configuration
			Properties properties = PropertiesLoaderUtils.loadProperties(resource);
			// Loop configuration is similar to properties key:value
			for(Map.Entry<? ,? > entry : properties.entrySet()) { String factoryTypeName = ((String) entry.getKey()).trim();// Comma-separated lists into string arrays
				String[] factoryImplementationNames =
						StringUtils.commaDelimitedListToStringArray((String) entry.getValue());	
				// Loop the value subitem into the list
				for (String factoryImplementationName : factoryImplementationNames) {
					result.computeIfAbsent(factoryTypeName, key -> newArrayList<>()) .add(factoryImplementationName.trim()); }}}// The list is de-duplicated
		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 the code

Get the key/value pairs from the cache according to the different classloaders, and return the contents of the classloaders if they exist. If no key-value pairs are found, the content of any meta-INF/Spring. factories file available to the classloader is retrieved, parsed one by one and put in the cache for the next retrieval.

As you can see from this part of the method, SpringBoot sets some default interface classes and implementation classes into the configuration file, similar to the way the properties file was often used.

Such as:

example.MyService=example.MyServiceImpl1,example.MyServiceImpl2

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

Get the Spring factory instance

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

private <T> Collection<T> getSpringFactoriesInstances(Class
       
         type, Class
        [] parameterTypes, Object... args)
        {
	// Get the class loader
	ClassLoader classLoader = getClassLoader();
	// Use the name and make it unique to prevent duplication
	// 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 the code

Get the class loader

org.springframework.boot.SpringApplication#getClassLoader

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

Get the Spring factory instance

org.springframework.boot.SpringApplication#createSpringFactoriesInstances

private <T> List<T> createSpringFactoriesInstances(Class
       
         type, Class
        [] parameterTypes, ClassLoader classLoader, Object[] args, Set
        
          names)
        
        {
	// Create a list of instances
	List<T> instances = new ArrayList<>(names.size());
	// Loop the list
	for (String name : names) {
		try {
			// Get the Class based on the Class nameClass<? > instanceClass = ClassUtils.forName(name, classLoader);// Determine whether subclasses are assignable
			Assert.isAssignable(type, instanceClass);
			// Get the constructorConstructor<? > constructor = instanceClass.getDeclaredConstructor(parameterTypes);// instantiate the object
			T instance = (T) BeanUtils.instantiateClass(constructor, args);
			// Place the instantiation result in the instantiation list
			instances.add(instance);
		}
		catch (Throwable ex) {
			throw new IllegalArgumentException("Cannot instantiate " + type + ":"+ name, ex); }}return instances;
}
Copy the code

Creating a boot context

org.springframework.boot.SpringApplication#createBootstrapContext

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

Instantiate the 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]; }}returnctor.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 the code

There is a lot of Spring source code involved in this part, but the main one is the ctor.newinstance (argsWithDefaultValues) method, which instantiates objects with constructors.

Spring break

org.springframework.context.support.AbstractApplicationContext#refresh

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

		// Ready to refresh
		// tip: Set some parameters without looking too closely
		prepareRefresh();

		// Tells subclasses to refresh the internal bean factory
		// Get the refresh bean factory
		ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

		// Prepare the bean factory
		prepareBeanFactory(beanFactory);

		try {
			// Allows post-processing of bean factories in context subclasses.
			// tip: This section deals with starting Web servers, such as servlets
			postProcessBeanFactory(beanFactory);

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

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

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

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

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

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

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

			// The final 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 the code

This section is too deep to read and covers Spring.

The resources

Java. Awt. Headless mode the most authoritative network Spring+Spring Boot source parsing! Ali Senior Architects will master Spring and Spring Boot in 450 minutes.

Any errors in this article are welcome to correct! Progress together!

Finally do a small advertisement, there are interested in Java development, you can add groups together to learn and communicate!

QQ: 425343603