Wechat official account: GLmapper Studio Gold digging column: Glmapper Micro-blog: Crazy stone _Henu Welcome to pay attention to, learn together, share together
As a very popular microservices framework, SpringBoot makes it very easy to build independent Spring production-grade applications, so it is favored by many Internet enterprises.
Recommended reading
- SpringBoot series -FatJar startup principle
- SpringBoot series – Startup process analysis
- SpringBoot series – Event mechanism details
- SpringBoot series – Embedded Tomcat implementation principle analysis
- SpringBoot series -Kafka introduction & integrated SpringBoot
background
SOFATracer integration Spring Cloud Stream RocketMQ How to modify a Bean when the BeanPostProcessor is not in effect is related to the life cycle of the Bean, and of course the process of container startup. SpringBoot startup process for me is not strange, can also be said to be more familiar, but before the complete combing of this one thing, and then the actual application process will inevitably step on some pits. Also think of before also wrote a SpringBoot series – FatJar startup principle, just to undertake the previous, continue to explore SpringBoot in some knowledge points.
Note: This article is based on SpringBoot 2.1.0.release, there may be differences between various versions of SpringBoot, but the general process is basically the same, so you see the actual working process is also
Start the entrance
In this article, JarLaunch builds an instance of MainMethodRunner and calls the main method in the BootStrap class by reflection. The ‘main method in the BootStrap class’ is actually the business entry to SpringBoot, as seen in the following code snippet:
@SpringBootApplication
public class GlmapperApplication {
public static void main(String[] args) {
SpringApplication.run(GlmapperApplication.class, args);
}
}
Copy the code
As you can see intuitively from the code, startup is done by calling the SpringApplication static method run; The run method internally constructs an instance of SpringApplication and then calls the run method of that instance to launch SpringBoot.
/ * *
* Static helper that can be used to run a {@link SpringApplication} from the
* specified sources using default settings and user supplied arguments.
* @param primarySources the primary sources to load
* @param args the application arguments (usually passed from a Java main method)
* @return the running {@link ApplicationContext}
* /
public static ConfigurableApplicationContext run(Class<? >[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}
Copy the code
Therefore, if we want to analyze the startup process of SpringBoot, we need to be familiar with the construction process of SpringApplication and the execution process of the Run method of SpringApplication.
Build a SpringApplication instance
For reasons of length, we only analyze the core build process.
public SpringApplication(ResourceLoader resourceLoader, Class
... primarySources) {
// Resource loader, default is null
this.resourceLoader = resourceLoader;
// Start the class bean
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// Whether it is a Web application
this.webApplicationType = WebApplicationType.deduceFromClasspath();
/ / set the ApplicationContextInitializer
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
/ / set the ApplicationListener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
/ / start the class
this.mainApplicationClass = deduceMainApplicationClass();
}
Copy the code
In the code snippet above, there are two points to focus on:
- 1, initialize ApplicationContextInitializer;
- 2. Initialize ApplicationListener
Note that this instantiation is not done through annotations and package sweeps, but through a loading method that is independent of the Spring context; This is done so that various configurations can be done before Spring is finished booting. The Spring workaround is to log the fully qualified name of the interface as the key and the fully qualified name of the implementation class as the value in the meta-INF/Spring.Factories file of the project. The spring FactoriesLoader utility class provides static methods for class loading and caching. Spring. factories is the core configuration file for Spring Boot. The SpringFactoriesLoader can be understood as an SPI extension implementation provided by Spring itself. The default spring.Factories configuration provided in SpringBoot is as follows:
# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
/ /.. omit
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
/ /.. omit
# Error Reporters
org.springframework.boot.SpringBootExceptionReporter=\
/ /.. omit
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\/
/ /.. omit
# Application Listeners
org.springframework.context.ApplicationListener=\
/ /.. omit
# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
/ /.. omit
# Failure Analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
/ /.. omit
# FailureAnalysisReporters
org.springframework.boot.diagnostics.FailureAnalysisReporter=\
/ /.. omit
Copy the code
There is no more analysis here of how the SpringFactoriesLoader loads these resources, but interested readers can check out the source code for themselves. org.springframework.core.io.support.SpringFactoriesLoader#loadSpringFactories
Run method main flow
Here’s an intuitive look at the code, then break it down one by one:
public ConfigurableApplicationContext run(String... args) {
// Start the container startup timer
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
/ / SpringBootExceptionReporter list, SpringBoot allows custom Reporter
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// Set the java.awt.headless property to true or false
/ / can see the explanation: https://blog.csdn.net/michaelgo/article/details/81634017
configureHeadlessProperty();
/ / get all SpringApplicationRunListener, is also obtained by SpringFactoriesLoader
SpringApplicationRunListeners listeners = getRunListeners(args);
// Issue the starting event, which is called immediately when the run method is first started and can be used for very early initialization before the container context is refreshed
listeners.starting();
try {
// Build the ApplicationArguments object
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
// Prepare the environment attributes required by the context refresh - see prepareEnvironment process analysis
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
// spring.beaninfo.ignore, set to true if null
configureIgnoreBeanInfo(environment);
// Prints the SpringBoot boot Banner
Banner printedBanner = printBanner(environment);
// Create a context, where different ApplicationContexts are created based on the webApplicationType
context = createApplicationContext();
// 加载获取 exceptionReporters
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// Prepare for context refresh - see The prepareContext process analysis
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
// refreshContext - see refreshContext procedure analysis
refreshContext(context);
This method is an empty implementation in SpringBoot and can be extended by itself
afterRefresh(context, applicationArguments);
// Stop the timer
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
// Publish the started event
listeners.started(context);
// ApplicationRunner and CommandLineRunner calls
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
// Exception handling
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
// Publish the running event
listeners.running(context);
}
catch (Throwable ex) {
// Exception handling
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
Copy the code
The code above is basically made some simple comments, there are a few points need to pay attention to:
- 1. Processing process of prepareEnvironment
- 2. PrepareContext processing
- 3. Process of refreshContext
- 4. Timing and sequence of implementation
- 5. Exception handling logic
The timing and sequence of Process execution have been analyzed in detail in previous articles: SpringBoot Series – Event Mechanisms. The following is a detailed analysis of the other four points.
Analysis of the startup process is essentially an understanding of the entire container life cycle, including the timing of events executed, PostProcessor execution, Enviroment Ready, and so on. Mastering these extensions and timing allows you to do a lot in real business development.
The process of prepareEnvironment
The prepareEnvironment process is relatively new, providing the Environment for context refreshes.
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
// Configure PropertySources and Profiles
// 1. Configure the parameters and some default attributes to the environment
// 2. Enable Profiles
configureEnvironment(environment, applicationArguments.getSourceArgs());
/ / release ApplicationEnvironmentPreparedEvent event
listeners.environmentPrepared(environment);
// Bind the SpringApplication environment
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader())
.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
// The attached parser will dynamically track any additions or deletions of the underlying Environment attribute source
ConfigurationPropertySources.attach(environment);
return environment;
}
Copy the code
What this does is package our configuration, including system configuration, application.properties, -d parameters, and so on, into the environment. In Spring, we use ${XXX} in our most common XML or @value (“${XXXX}”) in our code to get the Value from the environment.
Here need to focus on one of the more important point is to release ApplicationEnvironmentPreparedEvent events, we can use to monitor this event to change the environment. Here you can under reference SOFATracer SofaTracerConfigurationListener is how to use this event to do environment configuration processing.
The processing of prepareContext
There’s a whole bunch of points that you can take advantage of in preparing Context, Such as ApplicationContextInitializer execution, ApplicationContextInitializedEvent and ApplicationPreparedEvent event publishing.
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
// Set the environment to the context, so note that in the previous context, there is no environment.
context.setEnvironment(environment);
// Post-processing of ApplicationContext, such as registering BeanNameGenerator and ResourceLoader
postProcessApplicationContext(context);
/ / start executing all ApplicationContextInitializer here
applyInitializers(context);
/ / release ApplicationContextInitializedEvent event
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if(printedBanner ! =null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
/ / whether to allow the bean, if here is false, is likely to lead to abnormal BeanDefinitionOverrideException
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
// Publish the ApplicationPreparedEvent event
listeners.contextLoaded(context);
}
Copy the code
Initialization ApplicationContextInitializer is the spring container to refresh before spring ConfigurableApplicationContext callback interface, ApplicationContextInitializer until the execution and the initialize method, the context is not refresh. Can see and then released after applyInitializers ApplicationContextInitializedEvent events. In fact this two points can be context for some things, ApplicationContextInitializer more pure, it focuses on the context; And ApplicationContextInitializedEvent event source in addition to the context, and args springApplication object and parameters.
The final stage of prepareContext is to publish the ApplicationPreparedEvent event, indicating that the context is ready to execute Refresh at any time.
RefreshContext processing
RefreshContext is Spring context refresh process, here is the actual call AbstractApplicationContext refresh method; So SpringBoot also reuses the Spring context refresh process.
@Override
public void refresh(a) throws BeansException, IllegalStateException {
// lock
synchronized (this.startupShutdownMonitor) {
// Ready to refresh this context. This includes placeholder replacement and validation of all properties
prepareRefresh();
// There are many things done here:
/ / 1, let subclasses refresh the beanFactory, create the IoC container (DefaultListableBeanFactory - ConfigurableListableBeanFactory implementation class)
// load the parsed XML file (finally stored in the Document object)
// 3. Read the Document object and complete the loading and registration of BeanDefinition
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Do some preprocessing on the beanFactory (set some public properties)
prepareBeanFactory(beanFactory);
try {
/ / allow subclasses in AbstractApplicationContext post processing of the BeanFactory postProcessBeanFactory () this method implementation is empty.
postProcessBeanFactory(beanFactory);
// Call BeanFactoryPostProcessor to process BeanFactory instance (BeanDefinition)
invokeBeanFactoryPostProcessors(beanFactory);
// Register BeanPostProcessor, which is used to create intercepting beans
// Used to process the created bean instance
registerBeanPostProcessors(beanFactory);
// Initialize the message resource
initMessageSource();
// Initializes the application event broadcaster
initApplicationEventMulticaster();
Special bean / / is initialized, this method is empty, let AbstractApplicationContext subclasses override
onRefresh();
// Register listener (ApplicationListener)
registerListeners();
// Instantiate the remaining singleton beans (non-lazy-loaded). IoC, DI, and AOP of beans all occur in this step
finishBeanFactoryInitialization(beanFactory);
// Complete the refresh
// 1, publish ContextRefreshedEvent event
// 2, handle LifecycleProcessor
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid resource hanging.
destroyBeans();
// Reset 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 bean
resetCommonCaches();
}
}
}
Copy the code
There are a lot of things involved, a lot of scalable points, Including BeanFactoryPostProcessor processing, BeanPostProcessor processing, LifecycleProcessor processing released ContextRefreshedEvent events. At this point the container refresh is complete, the container is ready, and DI and AOP are complete.
Spring BeanFactoryPostProcessor processing
The BeanFactoryPostProcessor can modify all the BeanDefinition (not instantiated) data in our beanFactory before the bean is instantiated. So here, we can register some BeanDefinitions ourselves, and we can make some changes to beanDefinitions as well. The use of BeanFactoryPostProcessor can be found in many frameworks. Here is an example of modifying Datasource in SOFATracer.
SOFATracer in to a buried point source, based on JDBC specification provides a DataSourceBeanFactoryPostProcessor, used to modify the original DataSource to implement a layer of the agent. Code see: com. Alipay. Sofa. Tracer. The boot. The datasource. Processor. DataSourceBeanFactoryPostProcessor
The postProcessBeanFactory method creates different datasourceProxies based on the type of Datasource. The process of creating a DataSourceProxy is the process of modifying the original Datasource.
private void createDataSourceProxy(ConfigurableListableBeanFactory beanFactory,
String beanName, BeanDefinition originDataSource,
String jdbcUrl) {
// re-register origin datasource bean
BeanDefinitionRegistry beanDefinitionRegistry = (BeanDefinitionRegistry) beanFactory;
// Remove the BeanDefinition of the existing Datasource
beanDefinitionRegistry.removeBeanDefinition(beanName);
boolean isPrimary = originDataSource.isPrimary();
originDataSource.setPrimary(false);
// Change the beanName and re-register with the container
beanDefinitionRegistry.registerBeanDefinition(transformDatasourceBeanName(beanName),
originDataSource);
// Build the broker's datasource BeanDefinition of type SmartDataSource
RootBeanDefinition proxiedBeanDefinition = new RootBeanDefinition(SmartDataSource.class);
// Set the BeanDefinition attribute
proxiedBeanDefinition.setRole(BeanDefinition.ROLE_APPLICATION);
proxiedBeanDefinition.setPrimary(isPrimary);
proxiedBeanDefinition.setInitMethodName("init");
proxiedBeanDefinition.setDependsOn(transformDatasourceBeanName(beanName));
// Get the attributes of the original datasource
MutablePropertyValues originValues = originDataSource.getPropertyValues();
MutablePropertyValues values = new MutablePropertyValues();
String appName = environment.getProperty(TRACER_APPNAME_KEY);
// Modify and add attributes
Assert.isTrue(! StringUtils.isBlank(appName), TRACER_APPNAME_KEY +" must be configured!");
values.add("appName", appName);
values.add("delegate".new RuntimeBeanReference(transformDatasourceBeanName(beanName)));
values.add("dbType".
DataSourceUtils.resolveDbTypeFromUrl(unwrapPropertyValue(originValues.get(jdbcUrl))));
values.add("database".
DataSourceUtils.resolveDatabaseFromUrl(unwrapPropertyValue(originValues.get(jdbcUrl))));
// Set the new values to the agent BeanDefinition
proxiedBeanDefinition.setPropertyValues(values);
// Register the broker's datasource BeanDefinition with the container
beanDefinitionRegistry.registerBeanDefinition(beanName, proxiedBeanDefinition);
}
Copy the code
This code is BeanFactoryPostProcessor, a typical application scenario where you modify the BeanDefinition.
BeanFactoryPostProcessor is a long process, so I don’t want to analyze the process here. 1. What BeanFactoryPostProcessor does and what it can do; 2. At what stage of the container startup is it executed.
RegisterBeanPostProcessors process
RegisterBeanPostProcessors is used to register the BeanPostProcessor. BeanPostProcessor works later than BeanFactoryPostProcessor. BeanFactoryPostProcessor deals with BeanDefinition and Bean has not been instantiated. BeanPostProcessor deals with beans, and the BeanPostProcessor includes two methods for callbacks before and after Bean instantiation.
As mentioned earlier, there are some scenarios where BeanPostProcessor does not work. For Spring, BeanPostProcessor itself would be registered as a Bean, so it would be a natural possibility, The BeanPostProcessor handles cases where the bean is completed before the BeanPostProcessor itself initializes.
RegisterBeanPostProcessors can be divided into the following several parts:
- Registered BeanPostProcessorChecker. (When a bean is created during BeanPostProcessor instantiation, that is, when a bean is not eligible for processing by all BeanPostProcessors, it records an informational message)
- The BeanPostProcessor that implements prioritization, sorting, and other operations
- Register a BeanPostProcessor that implements PriorityOrdered
- Register the Ordered implementation
- Register all regular BeanpostProcessors
- Re-register all internal BeanpostProcessors
- Register the post-handler as an ApplicationListener to detect the internal bean, move it to the end of the processor chain (to get the proxy, etc.).
Here again, the extension timing is the main line, and the IoC, DI, and AOP initialization processes of beans are not covered in detail.
LifecycleProcessor processing
LifecycleProcessor’s processing is executed in the finishRefresh method, which we’ll look at first:
protected void finishRefresh(a) {
// Clear the context-level resource cache (such as scanned ASM metadata).
clearResourceCaches();
// Initialize the LifecycleProcessor for this context.
initLifecycleProcessor();
// First propagate refresh to the LifecycleProcessor.
getLifecycleProcessor().onRefresh();
// Publish the ContextRefreshedEvent event
publishEvent(new ContextRefreshedEvent(this));
// Participate in LiveBeansView MBean, if active.
LiveBeansView.registerApplicationContext(this);
}
Copy the code
Initializing initLifecycleProcessor takes all LifecyCleProcessors from the container. If there is no bean in the business code that implements the LifecycleProcessor interface, Use the default DefaultLifecycleProcessor.
The onRefresh procedure is the end call to the Start method on the Lifecycle interface. LifeCycle defines the LifeCycle of a Spring container object, and any Spring-managed object can implement this interface. Then, when the ApplicationContext itself receives a start and stop signal (such as a stop/restart scenario at runtime), the Spring container will find all the classes in the container context that implement LifeCycle and its subclasses and call the classes that they implement. Spring does this by delegating to the LifecycleProcessor LifecycleProcessor. Lifecycle interface is defined as follows:
public interface Lifecycle {
/ * *
* Start the current component
If the component is already running, you should not throw an exception
* 2. For containers, this propagates the start signal to all components of the application
* /
void start(a);
/ * *
* This component is usually stopped synchronously, and when this method is complete, the component is stopped completely. Consider implementing SmartLifecycle and its stop when asynchronous stop behavior is required
* (Runnable) method variant. Note that this stop notice is not guaranteed to arrive prior to destruction: during routine shutdown, {@codeLifecycle} beans will first receive a stop notification before propagating
* Regular destruction callback; However, only the destruction method is called on a hot refresh or aborted refresh attempt during the context's lifetime. For containers, this propagates the stop signal to all components of the application
* /
void stop(a);
/ * *
* Check if this component is running.
* 1. The start method is executed only if the method returns false.
* 2. The stop(Runnable callback) or stop() methods will only be executed if the method returns true.
* /
boolean isRunning(a);
}
Copy the code
At this point, the container refresh is actually complete. You can see that Spring or SpringBoot has a lot of holes exposed during the entire boot process for the user to use, very flexible.
Exception handling logic
Like the normal process, the exception handling process is part of the SpringBoot life cycle, and when an exception occurs, there are mechanisms to handle the end of the process. There is a large difference between SpringBoot 1.x and SpringBoot 2.x. Only SpringBoot 2.x processing is analyzed here. Here’s a code:
private void handleRunFailure(ConfigurableApplicationContext context,
Throwable exception,
Collection<SpringBootExceptionReporter> exceptionReporters,
SpringApplicationRunListeners listeners) {
try {
try {
// exitCode
handleExitCode(context, exception);
if(listeners ! =null) {
// failed
listeners.failed(context, exception);
}
}
finally {
// This is also an extension port
reportFailure(exceptionReporters, exception);
if(context ! =null) {
context.close();
}
}
}
catch (Exception ex) {
logger.warn("Unable to close ApplicationContext", ex);
}
ReflectionUtils.rethrowRuntimeException(exception);
}
Copy the code
The above snippet does a few things:
- Get abnormal exitCode handleExitCode: here, then released a ExitCodeEvent events, finally in the SpringBootExceptionHandler’s hands.
- SpringApplicationRunListeners# failed: iterate over all SpringApplicationRunListener failed method call
- ReportFailure: users can custom extensions SpringBootExceptionReporter interface to implement custom exception reporting logic
In SpringApplicationRunListeners# failed in business the exception will be thrown directly, without affecting the main process of exception handling.
conclusion
At this point, the main process of SpringBoot startup has been completely analyzed. From an extension and extension timing point of view, SpringBoot provides a number of extensions that allow users to do customized operations at various stages of container startup (whether startup, environment preparation, container refresh, etc.). Users can take advantage of these extended interfaces to modify beans and environment variables, giving users a lot of room.