Spring Boot, as the most popular Java development framework at present, adheres to the principle of “convention is better than configuration”, greatly simplifies the tedious XML file configuration of Spring MVC, and basically realizes zero configuration startup projects.

Spring Boot 2.1.0.RELEASE

Let’s first look at the simplest Spring Boot startup code

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); }}Copy the code

Everyone who has used Spring Boot should be familiar with the above code, which can start the Spring Boot application. So what’s going on inside SpringApplication.run(demoApplication.class, args)?

Before looking at the specific code, let’s take a look at the internal execution flow of SpringApplication, as shown below

Can be seen from the diagram above the run () is the entry of the application, and then initialize SpringApplicationRunListener, Environment and other instances, then create a context object, “preparation” and “refresh” context, here the Spring container has basically start to finish, Finally, an event is sent to inform each component to act accordingly.

Source code analysis

After understanding the general process, the following start in-depth source analysis of Spring Boot specific Boot process, first enter the entry method run

public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    configureHeadlessProperty();
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    // ...
Copy the code

StopWatch collects statistics on the execution duration of each task, for example, the total Spring Boot startup duration.

Started DemoApplication in 4.241 seconds (JVM running for 5.987)

GetRunListeners () finished SpringApplicationRunListener instantiation work, how to do? Enter the internal view of the method

private SpringApplicationRunListeners getRunListeners(String[] args) { Class<? >[] types =newClass<? >[] { SpringApplication.class, String[].class };return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
				SpringApplicationRunListener.class, types, this, args));
}
Copy the code

SpringApplicationRunListeners and SpringApplicationRunListener is not the same class, their names are very similar

Check the SpringApplicationRunListeners source

SpringApplicationRunListeners(Log log,
			Collection<? extends SpringApplicationRunListener> listeners) {
	this.log = log;
	this.listeners = new ArrayList<>(listeners);
}

public void starting(a) {
	for (SpringApplicationRunListener listener : this.listeners { listener.starting(); }}public void environmentPrepared(a) {
	/ /...
}
public void contextPrepared(a) {
	/ /...
}
public void contextLoaded(a) {
	/ /...
}
public void started(a) {
	/ /...
}
public void running(a) {
	/ /...
}
Copy the code

It is a collection of SpringApplicationRunListener

Observe SpringApplicationRunListeners all methods, it can be seen that it is actually a tool used for sending SpringApplicationRunListener related events

Then continue to observe getSpringFactoriesInstances source code, it is how to instantiate objects (this method more than subsequent use)

private <T> Collection<T> getSpringFactoriesInstance(Class
       
         type, Class
        [] parameterTypes, Object... args)
        {
	ClassLoader classLoader = getClassLoader();
	// Load the object name
	Set<String> names = new LinkedHashSet<>(
			SpringFactoriesLoader.loadFactoryNames(type,classLoader));
	List<T> instances = createSpringFactoriesInstances(type parameterTypes,
			classLoader, args, names);
	AnnotationAwareOrderComparator.sort(instances);
	return instances;
}
Copy the code

Here by SpringFactoriesLoader. LoadFactoryNames obtain corresponding FactoryNames type, don’t know what’s the use? Enter the internal view of the method

public static List<String> loadFactoryNames(Class
       factoryClass, @Nullable ClassLoader classLoader) {
	String factoryClassName = factoryClass.getName();
	return loadSpringFactories(classLoader).getOrDefaul(factoryClassName, Collections.emptyList());
}
Copy the code

Continue inside the loadSpringFactories method

public static final String FACTORIES_RESOURCE_LOCATION ="META-INF/spring.factories";
private static Map<String, List<String>> loadSpringFactorie(@Nullable ClassLoader classLoader) {
	MultiValueMap<String, String> result = cache.ge(classLoader);
	if(result ! =null) {
		return result;
	}
	try {
		// Get the resource for meta-INF /spring.factoriesEnumeration<URL> urls = (classLoader ! =null ?
				classLoader.getResource(FACTORIES_RESOURCE_LOCATION) :
				ClassLoader.getSystemResource(FACTORIES_RESOURCE_LOCATION));
		result = new LinkedMultiValueMap<>();
		while (urls.hasMoreElements()) {
			URL url = urls.nextElement();
			UrlResource resource = new UrlResource(url);
			// Read the contents of the file
			Properties properties =PropertiesLoaderUtils.loadProperties(resource);
			for(Map.Entry<? ,? > entry : properties.entrySe()) { String factoryClassName = ((String)entry.getKey()).trim();for (String factoryName :StringUtils.commaDelimitedListToStringArray(String) entry.getValue())) {
					// Get multiple valUs for factoryClassName (multiple values separated by commas)result.add(factoryClassName,factoryName.trim()); }}}// Cache the content that has been read
		cache.put(classLoader, result);
		return result;
	}
	catch (IOException ex) {
		throw new IllegalArgumentException("Unable to loadfactories from location [" +
				FACTORIES_RESOURCE_LOCATION + "]", ex); }}Copy the code

Where is the meta-INF /spring.factories file? What’s in the file?

This file is stored in the jar package of Spring Boot and Spring Boot Autoconfigure. It contains the following files:

# # complete content please check the original file Run Listeners org. Springframework. Boot. SpringApplicationRunListener = \ org.springframework.boot.context.event.EventPublishingRunListener # 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.ConfigFileApplicationListener,\ org.springframework.boot.context.config.DelegatingApplicationListener,\ org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\ org.springframework.boot.context.logging.LoggingApplicationListener,\ org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListenerCopy the code

Can see the corresponding value is EventPublishingRunListener SpringApplicationRunListener

Back to SpringFactoriesLoader. LoadFactoryNames within a method, which can be found the method to get the value of the actually factoryClass in the meta-inf/spring. The factories in the collection of the corresponding implementation class

Understand this method, and then back to getSpringFactoriesInstances method

private <T> Collection<T> getSpringFactoriesInstance(Class
       
         type, Class
        [] parameterTypes, Object... args)
        {
	ClassLoader classLoader = getClassLoader();
	/ / get SpringApplicationRunListener collection the realization of the corresponding to the name of the class
	Set<String> names = new LinkedHashSet<>(
			SpringFactoriesLoader.loadFactoryNames(type,classLoader));
	// Instantiate objects by reflection
	List<T> instances = createSpringFactoriesInstances(type parameterTypes,
			classLoader, args, names);
	AnnotationAwareOrderComparator.sort(instances);
	return instances;
}
Copy the code

So far getRunListeners completed SpringApplicationRunListener corresponding implementation class instantiation, and its starting is callback methods

SpringApplicationRunListeners listeners getRunListeners(args);
listeners.starting();
Copy the code

Learned from the above analysis, the call is actually EventPublishingRunListener starting method, then the method of internal do?

public void starting(a) {
	this.initialMulticaster.multicastEvent(
			new ApplicationStartingEvent(this.application,this.args));
}
Copy the code

An ApplicationStartingEvent event is sent

Continue looking for the consumers of the ApplicationStartingEvent event, and you can find all the predefined event consumers from Spring.Factories

# 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.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
Copy the code
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
Copy the code

The next step is to find the consumers of the ApplicationStartingEvent event from these consumers (the search is omitted) and find the following two consumers

  • LoggingApplicationListener initialization logging system

  • Liquibase LiquibaseServiceLocatorApplicationListener (parameters. The servicelocator. The servicelocator) if so, use the version of springboot related to replace

Once you understand the ApplicationStartingEvent event, go back to the Run method and explore prepareEnvironment

private ConfigurableEnvironment prepareEnvironment( SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
	// Create an Environment object
	ConfigurableEnvironment environment =getOrCreateEnvironment();
	configureEnvironment(environment,applicationArguments.getSourceArgs());
	/ / release ApplicationEnvironmentPreparedEvent event
	listeners.environmentPrepared(environment);
	bindToSpringApplication(environment);
	if (!this.isCustomEnvironment) {
		environment = new EnvironmentConverte(getClassLoader())
				.convertEnvironmentIfNecessary(environment,deduceEnvironmentClass());
	}
	ConfigurationPropertySources.attach(environment);
	return environment;
}
Copy the code

Here again issued a ApplicationEnvironmentPreparedEvent events, continue to find the event listeners

  • FileEncodingApplicationListener check coding system file format is in line with the environment variable configuration file encoding format (if there is a related Settings – spring. Mandatory – file – encoding), if the code does not conform to, throw an exception to stopSpringStart the
  • Whether AnsiOutputApplicationListener open AnsiOutput
  • DelegatingApplicationListener agent context. The listener. Classes configuration of the listener
  • The classpath ClasspathLoggingApplicationListener log output
  • LoggingApplicationListener configuration log system, logging. Config, logging. Level… Etc.
  • This is a relatively important ConfigFileApplicationListener monitoring objects, the specific method is as follows
private void onApplicationEnvironmentPreparedEvent( ApplicationEnvironmentPreparedEvent event) {
	List<EnvironmentPostProcessor> postProcessors =loadPostProcessors();
	postProcessors.add(this);
	AnnotationAwareOrderComparator.sort(postProcessors);
	for(EnvironmentPostProcessor postProcessor :postProcessors) { postProcessor.postProcessEnvironmen(event.getEnvironment(), event.getSpringApplication()); }}List<EnvironmentPostProcessor> loadPostProcessors(a) {
	return SpringFactoriesLoader.loadFactorie(EnvironmentPostProcessor.class,
			getClass().getClassLoader());
}
Copy the code

With spring.Factories, you can see that the following EnvironmentPostProcessor objects are loaded here

  • CloudFoundryVcapEnvironmentPostProcessor
  • SpringApplicationJsonEnvironmentPostProcessor
  • SystemEnvironmentPropertySourceEnvironmentPostProcessor
  • ConfigFileApplicationListener

Many students may doubt ConfigFileApplicationListener. There are no spring factories file, why do it here?

In fact ConfigFileApplicationListener onApplicationEnvironmentPreparedEvent method, will add to the list of EnvironmentPostProcessor object itself.

We focus on ConfigFileApplicationListener postProcessEnvironment method

public void postProcessEnvironment(ConfigurableEnvironmentenvironment, SpringApplication application) {
	addPropertySources(environment,application.getResourceLoader());
}
protected void addPropertySources(ConfigurableEnvironmentenvironment, ResourceLoader resourceLoader) {
	RandomValuePropertySource.addToEnvironment(environment);
	// Read applicaton. yml, application.properties and other configuration files
	new Loader(environment, resourceLoader).load();
}
Copy the code

ConfigFileApplicationListener after listening to ApplicationEnvironmentPreparedEvent events began to read the local configuration file

For details on how Spring reads local configuration files, go to Spring Boot source code

Create the ApplicationContext object

protected ConfigurableApplicationContextcreateApplicationContext(a) { Class<? > contextClass =this.applicationContextClass;
	if (contextClass == null) {
		try {
			// Create the corresponding context object according to webApplicationType
			switch (this.webApplicationType) {
			case SERVLET:
				contextClass = Class.forNam(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
				break;
			case REACTIVE:
				contextClass = Class.forNam(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
				break;
			default: contextClass = Class.forNam(DEFAULT_CONTEXT_CLASS); }}catch (ClassNotFoundException ex) {
			throw new IllegalStateException(
					"Unable create a defaultApplicationContext, "
							+ "please specify anApplicationContextClass", ex); }}return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
}
Copy the code

What type of ApplicationContext object is created based on webApplicationType. When is webApplicationType assigned?

public SpringApplication(ResourceLoader resourceLoader,Class
       ... primarySources) {
	this.resourceLoader = resourceLoader;
	Assert.notNull(primarySources, "PrimarySources must notbe null");
	this.primarySources = new LinkedHashSet<>(Arrays.asLis(primarySources));
	// Initializes webApplicationType
	this.webApplicationType =WebApplicationType.deduceFromClasspath();
	setInitializers((Collection) getSpringFactoriesInstance(
			ApplicationContextInitializer.class));
	setListeners((Collection) getSpringFactoriesInstance(ApplicationListener.class));
	this.mainApplicationClass = deduceMainApplicationClass(;
}
Copy the code

Can be seen from the above is through WebApplicationType deduceFromClasspath method initialized WebApplicationType, continue to follow the code

private static final String WEBFLUX_INDICATOR_CLASS = "org."
			+ "springframework.web.reactive.DispatcherHandler";
private static final String WEBMVC_INDICATOR_CLASS = "org.springframework."
			+ "web.servlet.DispatcherServlet";
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet"."org.springframework.web.context.ConfigurableWebApplicationContext" };

static WebApplicationType deduceFromClasspath(a) {
	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)) {
			returnWebApplicationType.NONE; }}return WebApplicationType.SERVLET;
}
Copy the code

As you can see from the code above, Spring determines the webApplicationType based on whether or not a class exists in the current classpath

Initialize the ApplicationContext object

private void prepareContext(ConfigurableApplicationContextcontext, ConfigurableEnvironment environment,SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, BannerprintedBanner) {
	// Initialize the context
	context.setEnvironment(environment);
	postProcessApplicationContext(context);
	applyInitializers(context);
	/ / send ApplicationContextInitializedEvent messages
	listeners.contextPrepared(context);
	if (this.logStartupInfo) {
		logStartupInfo(context.getParent() == null);
		logStartupProfileInfo(context);
	}
	// Add boot specific singleton beans
	ConfigurableListableBeanFactory beanFactory =context.getBeanFactory();
	beanFactory.registerSingleto("springApplicationArguments", applicationArguments);
	if(printedBanner ! =null) {
		beanFactory.registerSingleton("springBootBanner",printedBanner);
	}
	if (beanFactory instanceof DefaultListableBeanFactory) {
		((DefaultListableBeanFactory) beanFactory)
				.setAllowBeanDefinitionOverridin(this.allowBeanDefinitionOverriding);
	}
	Set<Object> sources = getAllSources();
	Assert.notEmpty(sources, "Sources must not be empty");
	/ / register DemoApplication
	load(context, sources.toArray(new Object[0]));
	listeners.contextLoaded(context);
}
Copy the code

Here DemoApplication is registered in the Spring container in preparation for subsequent bean scans

The next further refreshContext method, can be found is actually performed AbstractApplicationContext. Refresh method

public void refresh(a) throws BeansException,IllegalStateException {
	synchronized (this.startupShutdownMonitor) {
		prepareRefresh();
		ConfigurableListableBeanFactory beanFactory =obtainFreshBeanFactory();
		prepareBeanFactory(beanFactory);
		try {
			postProcessBeanFactory(beanFactory);
			// Finish loading the bean
			invokeBeanFactoryPostProcessors(beanFactory);
			registerBeanPostProcessors(beanFactory);
			initMessageSource();
			initApplicationEventMulticaster();
			onRefresh();
			registerListeners();
			finishBeanFactoryInitialization(beanFactory);
			finishRefresh();
		}
		catch (BeansException ex) {
			if (logger.isWarnEnabled()) {
				logger.warn("Exception encountered duringcontext initialization - " +
						"cancelling refresh attempt: " + ex;
			}
			destroyBeans();
			cancelRefresh(ex);
			throw ex;
		}
		finally{ resetCommonCaches(); }}}Copy the code

The REFRESH method does a lot of things internally. For example: Complete BeanFactory Settings, BeanFactoryPostProcessor, BeanPostProcessor interface callbacks, Bean loading, internationalization configuration, etc.

At this point, Spring basically completes the initialization of the container, and finally calls the callRunners method to execute the ApplicationRunner and CommandLineRunner interfaces.

private void callRunners(ApplicationContext context,ApplicationArguments args) {
	List<Object> runners = new ArrayList<>();
	runners.addAll(context.getBeansOfTyp(ApplicationRunner.class).values());
	runners.addAll(context.getBeansOfTyp(CommandLineRunner.class).values());
	AnnotationAwareOrderComparator.sort(runners);
	for (Object runner : new LinkedHashSet<>(runners)) {
		if (runner instanceof ApplicationRunner) {
			callRunner((ApplicationRunner) runner, args);
		}
		if (runner instanceofCommandLineRunner) { callRunner((CommandLineRunner) runner, args); }}}Copy the code

The core method of the entire startup process is Refresh, which internally holds most of the work needed to start the container. For space reasons, I will follow up with refresh internal source code analysis to understand the entire process of Spring Boot loading beans.