preface
To understand the loading process of SpirngBoot, we can better customize the SpringBoot loading process, and have certain reference significance for our own development of relevant architecture. The purpose of this article is to learn how SpringBoot completes the steps of loading the environment, setting up the context, and injecting beans from the boot class.
Due to my limited level, there may be omissions and mistakes in the analysis process, I hope you can directly point out, learn together, progress together.
run
No matter how you customize SpringBoot’s startup process, you always end up with the run() method. The following is an example:
public static void main(String[] args) throws NotSuchUserIdException {
SpringApplication springApplication = new SpringApplication(ApiApplication.class);
springApplication.addListeners(new ApplicationStartup());
springApplication.run(ApiApplication.class, args);
}
Copy the code
Obviously, SpringBoot does the environment configuration, context creation, and so on in the run method. So let’s go into this method. After the second jump, we come to this method:
public static ConfigurableApplicationContext run(Class
[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
Copy the code
This method completes two steps, one by creating the SpringApplication and one by executing the SpringApplication.run(args) method.
Let’s start by looking at what happens in the constructor of the SpringApplication:
public SpringApplication(ResourceLoader resourceLoader, Class
... primarySources) {
// A null is passed
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
/** * Confirm the types of Web applications. There are three types: * reactive * 2. Servlet * 3. None * SpringBoot's own forName method is used to determine whether the corresponding Web class exists. * If a jar package related to reactive exists, it is a Reactive Web type. * /
this.webApplicationType = WebApplicationType.deduceFromClasspath();
/** * setInitializers are variables that can be initialized in the class. * The following two methods are an analysis of this method. * /
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
/ / with getSpringFactoriesInstances method performs logic and getSpringFactoriesInstances almost
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// Get the class object of the currently started class
this.mainApplicationClass = deduceMainApplicationClass();
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, newClass<? > [] {}); }private <T> Collection<T> getSpringFactoriesInstances(Class
type, Class
[] parameterTypes, Object... args)
{
// Get the loader for its class based on the current thread
ClassLoader classLoader = getClassLoader();
/ * * * this method will be from the meta-inf/spring. The factories in the configuration file with the type * (ApplicationContextInitializer is the full path: * * org. Springframework. Context. ApplicationContextInitializer) as the key to obtain all values * /
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
/ * * * in createSpringFactoriesInstances () method, * through reflection to create each ApplicationContextInitializer * all instances of the class, and then added to the List to return. * /
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
Copy the code
We then focus on the run method:
public ConfigurableApplicationContext run(String... args) {
// Start timer, used to calculate how long the application takes to start
StopWatch stopWatch = new StopWatch();
// Start the timer
stopWatch.start();
// Environment context variables
ConfigurableApplicationContext context = null;
// Error report, when the startup error will be reported to the report
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// Set the java.awt.headless property
// This is used when the operating system is in HeadLess mode
/ / can see blog: http://jimolonely.github.io/2019/03/26/java/039-java-headless/
configureHeadlessProperty();
// Used to get runtime listeners, which I refer to here as listener groups
SpringApplicationRunListeners listeners = getRunListeners(args);
// Start the listener group
listeners.starting();
// Create ApplicationArguments object with only args value inside
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// Prepare the environment properties for context refresh
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
// Set spring.beaninfo.ignore in the environment variable to true/false
configureIgnoreBeanInfo(environment);
/ / print printBanner
Banner printedBanner = printBanner(environment);
// Create different context objects based on the Web type
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// Preparations before context refresh
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// Refresh the context
refreshContext(context);
// Refresh the context callback event
afterRefresh(context, applicationArguments);
// Stop the timer
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// Start event propagation
listeners.started(context);
// Call ApplicationRunner and CommandLineRunner
callRunners(context, applicationArguments);
// Start running event propagation
listeners.running(context);
return context;
}
Copy the code
As you can see, SpringBoot does a lot of processing, and we’ll go through the important methods step by step.
Create listeners
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
private SpringApplicationRunListeners getRunListeners(String[] args) { Class<? >[] types =newClass<? >[] { SpringApplication.class, String[].class };/ * * * new SpringApplicationRunListeners create a listener group. * getSpringFactoriesInstances method in the previous analysis, * role here is to find the meta-inf/spring. The key in the factories for * org springframework. Boot. SpringApplicationRunListener * class * /
return new SpringApplicationRunListeners(logger,
getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}
Copy the code
Load and configure the environment
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
/ / here for DefaultApplicationArguments created before
ConfigurableEnvironment environment = getOrCreateEnvironment();
// getSourceArgs returns the args passed in the run method
configureEnvironment(environment, applicationArguments.getSourceArgs());
/ * * * this listener group will get the corresponding through ApplicationEnvironmentPreparedEvent * this event listeners, and perform the monitoring method. This corresponds to the listener to * ConfigFileApplicationListener, it will configure the extension and the default configuration file name application, * Find the configuration files under classpath:/, classpath:/config/, file:./, file:./config/. * The default configuration file suffixes are Properties, YAMl, yML, XML. * /
listeners.environmentPrepared(environment);
// Bind the environment to the application
bindToSpringApplication(environment);
/ / this. IsCustomEnvironment to false
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
/** * If there is a configurationProperties property source, * does not exist by ConfigurationPropertySourcesPropertySource packaging < configurationProperties, SpringConfigurationPropertySources >, * It is then added to the first location of the collection in MutablePropertySources. * /
ConfigurationPropertySources.attach(environment);
return environment;
}
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
/ / this. AddConversionService to true
// Get a ConversionService through the singleton
// Set it to the environment
if (this.addConversionService) {
ConversionService conversionService = ApplicationConversionService.getSharedInstance();
environment.setConversionService((ConfigurableConversionService) conversionService);
}
// Check whether the new configuration source is specified (by command line)
configurePropertySources(environment, args);
// Obtain the activeProfile from spring.profiles.active in the configuration
configureProfiles(environment, args);
}
Copy the code
Creating an application Context
/** * Create a different default Web context based on the this.webApplicationType. * /
context = createApplicationContext();
Copy the code
Creating a bean object
// Context configuration before container refresh
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// This is the most important knowledge area, will be analyzed in detail later
refreshContext(context);
// This is an empty method
afterRefresh(context, applicationArguments);
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
// Load the previous environment into the context
context.setEnvironment(environment);
// Register context postprocessing,
postProcessApplicationContext(context);
/ / ApplicationContextInitializer execution
/ / can be inherited ApplicationContextInitializer class
/ / rewrite the initialize method, then the subclasses through application. AddInitializers
// Register with SpringApplication.
applyInitializers(context);
// The release context has preloaded the completion event
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// Register the ApplicationArguments class
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
// Register the Banner class
if(printedBanner ! =null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
/ / SpringApplication setAllowBeanDefinitionOverriding method
// to set whether BeanDefinition can be overwritten
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
// The release context has loaded the completed event
listeners.contextLoaded(context);
}
Copy the code
refresh
When we talked earlier about creating the Bean object, we saw that the refreshContext method is used to refresh the container. When we point into the method, after we found it is actually called the AbstractApplicationContext. The refresh method, saw the container into core execution logic part of the refresh. SpringBoot uses a template approach, so let’s look at its execution logic:
public void refresh(a) throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Pre-refresh processing
prepareRefresh();
/ / get the beanFactory
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Preprocess beanFactory
prepareBeanFactory(beanFactory);
try {
// Postprocess the beanFactory
postProcessBeanFactory(beanFactory);
// Call the beanFactory post-processor, in this case for BeanDefinition
invokeBeanFactoryPostProcessors(beanFactory);
// Register the bean afterhandler
registerBeanPostProcessors(beanFactory);
// internationalization processing
initMessageSource();
// Initializes the application event broadcaster
initApplicationEventMulticaster();
// Initialize the special bean. This method is an empty implementation
onRefresh();
// Register the listener
registerListeners();
// Instantiate the remaining beans, IOC, DI, and AOP all occur here
finishBeanFactoryInitialization(beanFactory);
// Complete the refresh and publish the event
finishRefresh();
}
catch (BeansException ex) {
// Destroy the created bean
destroyBeans();
// Cancel the active flag
cancelRefresh(ex);
throw ex;
}
finally {
// Reset the common self-check cache in the Spring kernel to clear the internal cache of the singleton beanresetCommonCaches(); }}}Copy the code
Next we will parse the implementation of its processing logic.
Prerefresh context
protected void prepareRefresh(a) {
// Get the context start time
this.startupDate = System.currentTimeMillis();
// Set the context to active
this.closed.set(false);
this.active.set(true);
// Initializes the upper and lower configuration information, which is an empty method
initPropertySources();
// Check whether required fields exist in the environment
getEnvironment().validateRequiredProperties();
/ / earlyApplicationListeners binding listeners
if (this.earlyApplicationListeners == null) {
this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners);
}
else {
this.applicationListeners.clear();
this.applicationListeners.addAll(this.earlyApplicationListeners);
}
this.earlyApplicationEvents = new LinkedHashSet<>();
}
Copy the code
To obtain the beanFactory
protected ConfigurableListableBeanFactory obtainFreshBeanFactory(a) {
refreshBeanFactory();
return getBeanFactory();
}
protected final void refreshBeanFactory(a) throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
// Create default beanFactory
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
// Set two properties:
// 1. allowBeanDefinitionOverriding
// 2. allowCircularReferences
customizeBeanFactory(beanFactory);
// Record all beans to beanFactory
// There are two main implementations:
// 1. XmlWebApplicationContext
// 2. AnnotationConfigWebApplicationContext
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory; }}catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for "+ getDisplayName(), ex); }}Copy the code
You can see that three things are done in the obtainFreshBeanFactory method:
- Create the beanFactory
- BeanDefinition has been loaded and registered.
The second configuration of the beanFactory is nothing to say.
PostProcessBeanFactory is used to modify the BeanDefinition data of the beanFactory, so we can register or modify some BeanDefinitions ourselves here. Rear invokeBeanFactoryPostProcessors method is invoked the beanFactory processor. The code is as follows:
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
/ * * * getBeanFactoryPostProcessors: get context beanFactoryProcessors * invokeBeanFactoryPostProcessors: Instantiate and call the registered beanFactoryProcessor */
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
/** * Check whether the bean named loadTimeWeaver is defined, and if so, add the * beanPostProcessor extension to the loadTimeWeaver function. And create a temporary classLoader to handle the real bean */
if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
beanFactory.setTempClassLoader(newContextTypeMatchClassLoader(beanFactory.getBeanClassLoader())); }}Copy the code
Rear registerBeanPostProcessors used to register the bean’s processor.
RegisterBeanPostProcessors execution process is as follows, omit the part of the code, but the logic is the same:
public static void registerBeanPostProcessors( ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) { // Get the beanName of the BeanPostProcessor implementation class String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true.false); // Register the PriorityOrdered implementation class sortPostProcessors(priorityOrderedPostProcessors, beanFactory); registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors); // Register the Ordered implementation class. List<BeanPostProcessor> orderedPostProcessors = new ArrayList<>(); for (String ppName : orderedPostProcessorNames) { BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class); orderedPostProcessors.add(pp); if (pp instanceof MergedBeanDefinitionPostProcessor) { internalPostProcessors.add(pp); } } sortPostProcessors(orderedPostProcessors, beanFactory); registerBeanPostProcessors(beanFactory, orderedPostProcessors); // Register other bean afterhandlers List<BeanPostProcessor> nonOrderedPostProcessors = new ArrayList<>(); for (String ppName : nonOrderedPostProcessorNames) { BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class); nonOrderedPostProcessors.add(pp); if (pp instanceof MergedBeanDefinitionPostProcessor) { internalPostProcessors.add(pp); } } registerBeanPostProcessors(beanFactory, nonOrderedPostProcessors); // Register all internal processors sortPostProcessors(internalPostProcessors, beanFactory); registerBeanPostProcessors(beanFactory, internalPostProcessors); / / register ApplicationListenerDetector beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext)); } Copy the code
Internationalization processing
protected void initMessageSource(a) {
/ / get the beanFactory
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
/** * Check whether the bean named messageSource * processing logic: * 1. If an implementation class for MessageSource exists, the MessageSource is initialized and assigned to its member variable MessageSource * 2. If no MessageSource implementation class exists, assign a DelegatingMessageSource to ensure that the getMessage * method call occurs normally. * /
if (beanFactory.containsLocalBean(MESSAGE_SOURCE_BEAN_NAME)) {
this.messageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME, MessageSource.class);
if (this.parent ! =null && this.messageSource instanceof HierarchicalMessageSource) {
HierarchicalMessageSource hms = (HierarchicalMessageSource) this.messageSource;
if (hms.getParentMessageSource() == null) { hms.setParentMessageSource(getInternalParentMessageSource()); }}}else {
DelegatingMessageSource dms = new DelegatingMessageSource();
dms.setParentMessageSource(getInternalParentMessageSource());
this.messageSource = dms;
beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource); }}Copy the code
Initialize the event to listen for multiplexers
protected void initApplicationEventMulticaster(a) {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
/ * * * judgment in the container if there is a name for applicationEventMulticaster * applicationEventMulticaster implementation class. If not, use the default SimpleApplicationEventMulticaster. * /
if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
this.applicationEventMulticaster =
beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
}
else {
this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster); }}Copy the code
Initialization special bean, is an empty method, this method is used to make AbstractApplicationContext subclasses override.
Initialize the listener
protected void registerListeners(a) {
// getApplicationListeners obtain listeners added by the user
for(ApplicationListener<? > listener : getApplicationListeners()) { getApplicationEventMulticaster().addApplicationListener(listener); }// Get all ApplicationListener implementation classes from the container
String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true.false);
for (String listenerBeanName : listenerBeanNames) {
getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
}
// Publish early listeners, null by default
Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
this.earlyApplicationEvents = null;
if(earlyEventsToProcess ! =null) {
for(ApplicationEvent earlyEvent : earlyEventsToProcess) { getApplicationEventMulticaster().multicastEvent(earlyEvent); }}}Copy the code
Instantiate the bean
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
// Initialize conversionService for the context
if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
beanFactory.setConversionService(
beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
}
// Check whether the context has valueResolver type converter
if(! beanFactory.hasEmbeddedValueResolver()) { beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal)); }// Initialize the LoadTimeWeaverAware bean
String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false.false);
for (String weaverAwareName : weaverAwareNames) {
getBean(weaverAwareName);
}
// Disable the use of temporary classloaders for type matching
beanFactory.setTempClassLoader(null);
// Allow caching of all bean definition data
beanFactory.freezeConfiguration();
// Prepare to instantiate the bean
// Go through the beanName list for each beanName
// Then instantiate the bean corresponding to the current beanName
beanFactory.preInstantiateSingletons();
}
Copy the code
The processing done by the container refresh
protected void finishRefresh(a) {
// Process the resource cache
clearResourceCaches();
// Initialize the lifecycle handler for the context
initLifecycleProcessor();
// Propagate the refresh close event to the lifecycle handler
getLifecycleProcessor().onRefresh();
// Push the context refresh completion event to the corresponding listener
publishEvent(new ContextRefreshedEvent(this));
// Register the context with mbeans, a Java technology
/ / reference blog: https://juejin.cn/post/6856949531003748365
LiveBeansView.registerApplicationContext(this);
}
Copy the code
conclusion
The main process of SpringBoot startup is analyzed. We can see that its structure is very strict, it also handles concurrency, and provides many interfaces for us to implement, in order to customize our own processing logic, the main design patterns used are factory mode and template mode. These are great inspirations for us to design our own website architecture later.