Spring Boot source code analysis II (startup process principle)
entrance
@SpringBootApplication public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } }
**SpringApplication.run(MyApplication.class, args);** Follow in
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); }
First, a new instance of SpringApplication will be created, and the startup class will be passed in as a parameter to execute its run method.
Constructor for spring application
Set application type
The Class loader is mainly used to judge whether the Class related to REACTIVE exists. If it does not exist, the web environment is SERVLET type. Set the web environment type here, and then initialize the corresponding environment according to the type.
Tomcat and spring webmvc will be introduced into the spring boot starter web pom, as follows
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <version>2.6.2</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.14</version> <scope>compile</scope> </dependency>
The dispatcher Servlet class exists in spring webmvc, which is the core Servlet of our previous spring MVC. The dispatcher Servlet class can be loaded through class loading, so our application type is naturally webapplicationtype Servlet
Set initializer
// Set initializers, which will be called at last setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
This method will try to get meta - inf / spring. From the classpath Read the corresponding configuration file at factories, and then traverse it. The Key in the read configuration file is: org springframework. context. value of applicationcontextinitializer. Take the spring boot autoconfigure package as an example, its meta-inf / spring The definitions of the factories section are as follows:
The two class names will be read out and put into the Set collection to start the following instantiation operations:
// parameterTypes: the names collection obtained in the previous step private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) { List<T> instances = new ArrayList<T>(names.size()); for (String name : names) { try { Class<?> instanceClass = ClassUtils.forName(name, classLoader); //Verify that the loaded class is a subclass of ApplicationContextInitializer Assert.isAssignable(type, instanceClass); Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes); //Reflect instanced objects T instance = (T) BeanUtils.instantiateClass(constructor, args); //Add to List collection instances.add(instance); } catch (Throwable ex) { throw new IllegalArgumentException( "Cannot instantiate " + type + " : " + name, ex); } } return instances; }
Confirm that the loaded class is really org springframework. context. Subclass of applicationcontextinitializer, then get the constructor to initialize, and finally put it into the instance list.
Therefore, the so-called initializer is org springframework. context. The implementation class of applicationcontextinitializer is defined as follows:
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> { void initialize(C applicationContext); }
Initialize before the Spring context is refreshed. Typically, for example, in a Web application, register Property Sources or activate profiles. Property Sources is easy to understand, that is, configuration files. Profiles is an entity abstracted by Spring to load different configuration items in different environments (such as DEV, TEST, PRODUCTION, etc.).
Set up listener
Let's start setting up the listener:
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
Let's follow up the code to see getSpringFactoriesInstances
// The input parameter type here is: org springframework. context. ApplicationListener. class private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) { return getSpringFactoriesInstances(type, new Class<?>[] {}); } private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // Use names and ensure unique to protect against duplicates Set<String> names = new LinkedHashSet<String>( SpringFactoriesLoader.loadFactoryNames(type, classLoader)); List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; }
It can be found that the process of loading the corresponding class name and then completing the instantiation is the same as that of setting the initializer above. Similarly, spring. In the spring boot autoconfigure package Take factories as an example to see the corresponding key value:
org.springframework.context.ApplicationListener=\ org.springframework.boot.autoconfigure.BackgroundPreinitializer 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
These 10 listeners will run through the entire life cycle of springBoot. At this point, the initialization process for the SpringApplication instance is over.
SpringApplication.run method
After the spring application instantiation is completed, start calling the run method.
- Step 1: get and start the listener
- Step 2: prepare the environment according to spring application runlisteners and parameters
- Step 3: create context
- Step 4: preprocessing context
- Step 5: refresh context
- Step 6: refresh the context again
- Step 7: issue an event to end execution
- Step 8: execute Runners
Get and start listener
Get listener
private SpringApplicationRunListeners getRunListeners(String[] args) { Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class }; return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args), this.applicationStartup); }
The getspringfactoryesinstances method is still used here to obtain instances. You can take a look at the previous method analysis from meta-inf / spring Read the Key in factories as org springframework. boot. Values of springapplicationrunlistener:
org.springframework.boot.SpringApplicationRunListener=
org.springframework.boot.context.event.EventPublishingRunListener
In getSpringFactoriesInstances, the constructor of EventPublishingRunListener will be triggered when the reflection gets the instance. Let's take a look at the constructor of EventPublishingRunListener:
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered { private final SpringApplication application; private final String[] args; //Broadcaster private final SimpleApplicationEventMulticaster initialMulticaster; public EventPublishingRunListener(SpringApplication application, String[] args) { this.application = application; this.args = args; this.initialMulticaster = new SimpleApplicationEventMulticaster(); Iterator var3 = application.getListeners().iterator(); while(var3.hasNext()) { ApplicationListener<?> listener = (ApplicationListener)var3.next(); //Add all the eleven listeners set to spring application above to the broadcaster SimpleApplicationEventMulticaster this.initialMulticaster.addApplicationListener(listener); } } //Slightly }
We can see that there is a broadcaster in * * EventPublishingRunListener. The construction method of EventPublishingRunListener adds all the eleven listeners of SpringApplication to the broadcaster simpleapplicationeventmulticast * * let's see how to add them to the broadcaster:
public abstract class AbstractApplicationEventMulticaster implements ApplicationEventMulticaster, BeanClassLoaderAware, BeanFactoryAware { //The parent class of the broadcaster is stored in the internal storage of the listener private final AbstractApplicationEventMulticaster.ListenerRetriever defaultRetriever = new AbstractApplicationEventMulticaster.ListenerRetriever(false); @Override public void addApplicationListener(ApplicationListener<?> listener) { synchronized (this.retrievalMutex) { Object singletonTarget = AopProxyUtils.getSingletonTarget(listener); if (singletonTarget instanceof ApplicationListener) { this.defaultRetriever.applicationListeners.remove(singletonTarget); } //Inner class object this.defaultRetriever.applicationListeners.add(listener); this.retrieverCache.clear(); } } private class ListenerRetriever { //Save all listeners public final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet(); public final Set<String> applicationListenerBeans = new LinkedHashSet(); private final boolean preFiltered; public ListenerRetriever(boolean preFiltered) { this.preFiltered = preFiltered; } public Collection<ApplicationListener<?>> getApplicationListeners() { LinkedList<ApplicationListener<?>> allListeners = new LinkedList(); Iterator var2 = this.applicationListeners.iterator(); while(var2.hasNext()) { ApplicationListener<?> listener = (ApplicationListener)var2.next(); allListeners.add(listener); } if (!this.applicationListenerBeans.isEmpty()) { BeanFactory beanFactory = AbstractApplicationEventMulticaster.this.getBeanFactory(); Iterator var8 = this.applicationListenerBeans.iterator(); while(var8.hasNext()) { String listenerBeanName = (String)var8.next(); try { ApplicationListener<?> listenerx = (ApplicationListener)beanFactory.getBean(listenerBeanName, ApplicationListener.class); if (this.preFiltered || !allListeners.contains(listenerx)) { allListeners.add(listenerx); } } catch (NoSuchBeanDefinitionException var6) { ; } } } AnnotationAwareOrderComparator.sort(allListeners); return allListeners; } } //Slightly }
The above method is defined in the parent class abstractapplicationeventmulticast of simpleapplicationeventmulticast. The key code is this defaultRetriever. applicationListeners. add(listener);, This is an internal class that holds all listeners. That is, in this step, spring Listeners in factories are passed to simpleapplicationeventmulticast. We now know that there is a broadcaster simpleapplicationeventmulticast in the eventpublishing runlistener, and all listeners are stored in the broadcaster simpleapplicationeventmulticast.
lsnrctl start
The listener we obtained through the getRunListeners method in the previous step is EventPublishingRunListener. From the name, we can see that it is a startup event publishing listener, which is mainly used to publish startup events.
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered { private final SpringApplication application; private final String[] args; private final SimpleApplicationEventMulticaster initialMulticaster;
Let's take a look at the spring applicationrunlistener interface first
package org.springframework.boot; public interface SpringApplicationRunListener { // When the run() method starts executing, it is called immediately and can be used to do some work at the earliest stage of initialization void starting(); // This method is called when the environment is built and before the ApplicationContext is created void environmentPrepared(ConfigurableEnvironment environment); // This method is called when the ApplicationContext is built void contextPrepared(ConfigurableApplicationContext context); // This method is called before the ApplicationContext completes loading but is not refreshed void contextLoaded(ConfigurableApplicationContext context); // After the ApplicationContext is refreshed and started, the method is called before CommandLineRunners and ApplicationRunner are called void started(ConfigurableApplicationContext context); // The run() method is called before its execution is complete void running(ConfigurableApplicationContext context); // This method is called when the application runs in error void failed(ConfigurableApplicationContext context, Throwable exception); }
The SpringApplicationRunListener interface is executed in various states during Spring Boot initialization. We can also add our own listeners to listen to events and execute custom logic during Spring Boot initialization. Let's take a look at the first startup event listeners during Spring Boot startup starting():
@Override public void starting() { //Key code, first create an application startup event ` ApplicationStartingEvent` this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args)); }
Here, we first create a startup event ApplicationStartingEvent. We continue to follow up with simpleapplicationeventmulticast. There are two core methods:
@Override public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) { ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event)); //Get the corresponding listener through the event type ApplicationStartingEvent for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) { //Gets the thread pool. If it is empty, it will be synchronized. The thread pool here is empty and has not been initialized. Executor executor = getTaskExecutor(); if (executor != null) { //Send events asynchronously executor.execute(() -> invokeListener(listener, event)); } else { //Send events synchronously invokeListener(listener, event); } } }
Here, the corresponding listener will be obtained according to the event type ApplicationStartingEvent, and the response action will be executed after the container is started.
Environment construction
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
Follow in:
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) { // Get the corresponding ConfigurableEnvironment ConfigurableEnvironment environment = getOrCreateEnvironment(); // to configure configureEnvironment(environment, applicationArguments.getSourceArgs()); ConfigurationPropertySources.attach(environment); listeners.environmentPrepared(bootstrapContext, environment); DefaultPropertiesPropertySource.moveToEnd(environment); Assert.state(!environment.containsProperty("spring.main.environment-prefix"), "Environment prefix cannot be set via properties."); bindToSpringApplication(environment); if (!this.isCustomEnvironment) { environment = convertEnvironment(environment); } ConfigurationPropertySources.attach(environment); return environment; }
Create container
context = createApplicationContext();
The judgment here is based on webApplicationType, which is SERVLET type, and the corresponding bytecode will be loaded through reflection, that is, annotationconfigservletwebserver ApplicationContext. webApplicationType has been obtained in the constructor before.
Spring container preprocessing
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
Call initializer
Here we use the initializers set when creating the SpringApplication instance, traverse them in turn, and call the initialize method. Of course, we can also customize the initializer, implement the initialize method, and then put it into meta-inf / spring The key in the factories configuration file is org springframework. context. In the value of applicationcontextinitializer, our customized initializer will be called to initialize our project.
Load start specified class
Here is to get this The primarysources attribute is our startup class myapplication Class, then look at load(context, sources.toArray(new Object[0]); method.
A subsequent article will introduce in detail how to load the startup class and the detailed process of automatic configuration.
Notifies the listener that the container is ready
listeners.contextLoaded(context);
This is mainly for the response processing of some listeners such as logs.
Refresh context
refreshContext(context);
At this point, the processing work related to Spring boot has been completed, and the next work is left to Spring.
The refresh method is the key to the implementation of ioc and aop, which will be analyzed in detail in subsequent articles.
Spring container post processing
afterRefresh(context, applicationArguments);
Extension interface, the template method in design pattern. It is empty by default. If you have custom requirements, you can override this method. For example, print some start and end log s, or some other post-processing.
Issue an event to end execution
listeners.started(context, timeTakenToStartup);
Get the EventPublishingRunListener listener, execute its started method, pass the created Spring container in, create an ApplicationStartedEvent event, and execute the publishEvent method of ConfigurableApplicationContext, that is, publish events in the Spring container, not in the Spring application, Unlike the previous starting, which directly publishes start events to 11 listeners in Spring application.
Execute Runners
callRunners(context, applicationArguments);
Spring boot provides two interfaces for users to expand: ApplicationRunner and CommandLineRunner. Implement its run method and inject it into the spring container. After the spring boot is started, all runner's run methods will be executed. It can be executed after the container is started (after the context is refreshed), and some operations similar to data initialization can be done.
private void callRunners(ApplicationContext context, ApplicationArguments args) { List<Object> runners = new ArrayList<Object>(); runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());//Gets the bean of ApplicationRunner type from the context runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());//Gets a bean of type CommandLineRunner from the context AnnotationAwareOrderComparator.sort(runners);//sort for (Object runner : new LinkedHashSet<Object>(runners)) { if (runner instanceof ApplicationRunner) { callRunner((ApplicationRunner) runner, args);//implement } if (runner instanceof CommandLineRunner) { callRunner((CommandLineRunner) runner, args); } } }
The two differences lie in different input parameters, which can be selected according to your actual situation.
public interface CommandLineRunner { void run(String... args) throws Exception; } public interface ApplicationRunner { void run(ApplicationArguments args) throws Exception; }
The execution parameter in CommandLineRunner is the String[] args string array parameter of the main method of the original java startup class; The parameters in ApplicationRunner are processed and some methods are provided, for example: List < string > getoptionvalues (string name);
} if (runner instanceof CommandLineRunner) { callRunner((CommandLineRunner) runner, args); } } }
The two differences lie in different input parameters, which can be selected according to your actual situation. ```java public interface CommandLineRunner { void run(String... args) throws Exception; } public interface ApplicationRunner { void run(ApplicationArguments args) throws Exception; }
The execution parameter in CommandLineRunner is the String[] args string array parameter of the main method of the original java startup class; The parameters in ApplicationRunner are processed and some methods are provided, for example: List < string > getoptionvalues (string name);
Get the value list according to the name. In the Java startup command -- foo=bar --foo=baz, return list["bar", "baz"] according to the foo parameter name