Article Directory
- foreword
- 1. What is Springboot
- 2. Startup process
-
- 2.1 Build Spring Boot project
- 2.2 Startup log
- 2.3 Startup process
-
- analysis description
- 2.3.1 Part 1: The constructor of SpringApplication
- 2.3.2 The second part: the operation of the run method
-
- A. Code implementation before printing banner
-
- A.2. Create a startup context
- A.3. Configure the attributes of no leader
- A.4. Get the running listener
- A.5. Publish the start event and handle it by the application start listener
- A.6. Prepare environment variables
-
- A.6.1 Get or create environment variables
- A.6.2 Configuring Environment Variables
- A.6.3 Dependency on configuring property sources and environment variables
- A.6.4 Monitoring environment preparation
- A.6.5 Move default environment variables to the end
- A.6.6 Binding the environment to a SpringApplication
- A.6.7 Converting the environment to standard environment variables
- A.7. Bean information ignored by configuration
- A.8. Print Banner information
- B. Code implementation after printing banner
-
- B.1. Create application context
- B.2. Prepare context
- B.3. Update context
-
- B.3.1. Prepare to update the context
- B.3.2. Tell the subclass to update the internal bean factory
- B.3.3. Bean factories ready to be used in this context
- B.3.4. Allow post-processing of bean factories in context subclasses
- B.3.5. Call the factory processor registered as a Bean in the context
- B.3.6. Registering the Bean Processor Created by the Interception Bean
- B.3.7. Initialize the message source of this context
- B.3.8. Initialize event broadcast for this context
- B.3.9. Initialize other special beans in specific context subclasses
- B.3.10. Check the Beans of the listener and register them
- B.3.11. Instantiate all remaining (non-lazy-loaded initialization) singletons
- B.3.12, the last step: release the corresponding event
- B.4. Post-processing of update context
- B.5. How long did it take for the printing application to start up?
- B.6. Publish the event of context start
- B.7. Invoking the Runner
- B.8. Handle caught running exceptions
- B.9. Publish ready events
- 3. Homework
- 4. Expansion
- V. Summary
foreword
After writing the preface, I paused for thousands of milliseconds, and suddenly I didn’t know what to say. The original intention of writing this blog was indeed the interview question that the interviewer once asked me. After reading the source code, no, I will make up the lesson. I am really busy at work, but this is not an excuse for me not to study. The summary is that I find that my inherent thinking still lacks the spirit of inquiring. In the future, I will develop a habit of asking why, remember!
1. What is Springboot
I decided to get to the bottom of it and go shopping on the official website~
I personally think that the best way to learn a technology and want to know more about it is the official website. If you don’t know anything about it, you can read what others wrote first. , After all, first look at some translations of the official website into words that are easy for Xiaobai to understand, which can eliminate our fear of learning new technologies (why should we be afraid, because we are afraid that we will not understand, feel stupid, and lose confidence).
It is said that springboot has been born for a long time. I really did not learn in a down-to-earth manner. Instead, many things stay in a place where I have only a little knowledge and a little taste. I will change this habit in the future. Remember!
Official website: https://spring.io/projects/spring-boot
In addition, you can also enter from Projects in https://spring.io :
There is no introduction to the Chinese version, so translate it:
OVERVIEW ( Overview )
Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”.
Spring Boot makes it easy to create self-contained, production-grade Spring-based applications that you can "one-click to run".
We take an opinionated view of the Spring platform and third-party libraries so you can get started with minimum fuss. Most Spring Boot applications need minimal Spring configuration.
We take a firm view on the Spring platform and third-party libraries, so you can get started with little effort. Most Spring Boot applications require minimal Spring configuration.
If you’re looking for information about a specific version, or instructions about how to upgrade from an earlier release, check out the project release notes section on our wiki.
If you're looking for information on a specific release, or instructions on how to upgrade from an earlier release, check out the project's Release Notes section on our wiki.
Features(特点)
Create stand-alone Spring applications
Create a standalone Spring application.
Embed Tomcat, Jetty or Undertow directly (no need to deploy WAR files)
Directly embed Tomcat, Jetty or Undertow (no need to deploy war files)
Provide opinionated ‘starter’ dependencies to simplify your build configuration
Provides "starter" dependencies to simplify build configuration
Automatically configure Spring and 3rd party libraries whenever possible
Automatically configure Spring and third-party libraries as much as possible
Provide production-ready features such as metrics, health checks, and externalized configuration
Provides production-ready features such as metrics, health checks, and externalized configuration
Absolutely no code generation and no requirement for XML configuration
Absolutely no code generation and no XML configuration required.
The process of translation is also the process of learning English. I really don’t know how to translate some words in it, so I used a dictionary.
Here I will learn English by the way. Although I digressed, I did not digress on the way of learning.
stand-alone independent
production-grade production-grade
just run one-click operation (paraphrased here)
take an opinionated view of something hold a firm view on some things
you can get started with minimum fuss you can start effortlessly Use
3rd party libraries
whenever possible
production-ready features production-ready features
Summary: SpringBoot can quickly build a Spring project to help developers focus more on business logic development.
2. Startup process
2.1 Build Spring Boot project
I won’t write about the creation process. I originally wanted to recreate a Spring boot by myself, but after I reinstalled the system, I could only install the community version of the idea first. There is no quick way to create it, but the springboot official website provides a way ( https ://start.spring.io ), just import it into idea after creation.
The place where the red box is drawn is the place I changed. After writing, click GENERATE at the bottom, and it will be downloaded automatically after the creation is completed.
After using the idea to open it, modify the address of the maven warehouse, wait for him to download and check the dependencies, the structure is as follows:
Of course, there are some useless files, which can be deleted or kept, which is not my focus this time.
2.2 Startup log
The log printed by direct run startup is as follows:
Seeing this familiar log, I have never thought about it before:
where is the jdk version of the first line printed?
Where is this spring logo printed?
Where is Tomcat loaded when it is initialized?
Where is the Servlet engine loaded at the beginning?
Where is the web application context loaded?
Where is the last successful startup loaded?
. . . . . .
Wait, let's start our treasure hunt and reveal the secrets one by one! ! !
2.3 Startup process
After reading it, I feel that it is divided into two parts: the constructor of SpringApplication and the operation of the run method.
analysis description
- I really don’t know if I don’t see it. When I look at the next jump, I didn’t think it was just a simple startup. In fact, there are too many things involved, and the main method I saw is really just the tip of the iceberg. Let’s finish writing this article with 53,000 words (of course, there are many method names that occupy the number of words), so the analysis process is very long-winded and involves a lot of detailed implementation (of course there are many things that have not been mentioned), mainly because I am the first To analyze the Springboot source code once, I want to clarify and record these processes during the analysis, so that I can sort out the knowledge system inside. The article is quite long. After all, I finished this blog after reading and writing it for more than a month, so I can read it several times when I read it. If you feel long-winded, you can directly read the title and jump to the part you want to read.
- Of course, for the purpose of learning rather than interviewing, if you have enough time and enough interest, it is strongly recommended that you follow the code execution process several times, because the eyes understand, but the hands do not move, and it will still be like a passing cloud in the end. The purpose of our looking at the source code, I feel that we can understand the ideas and excellent writing methods in it, and apply it to our own projects later to better serve the business.
- In addition, the analysis in this article is based on the analysis of the source code and corresponding comments. I still haven’t delved too deeply into the reasons for most of the implementations. I will take a look at it later when I have time and delve into the reasons for this design. Of course, the process I read I also wrote about the places I don’t understand, and the students who know it hope to enlighten me~
- How to eat this article: The whole startup process is really a lot, and it is eaten multiple times; because many places are connected, I also mentioned in the article where a certain code was analyzed. If you forget it, you can look back. At this time, you can Just press Ctrl+F to search.
2.3.1 Part 1: The constructor of SpringApplication
The startup entry of Springboot: the run method in the SpringBootDemoApplication.main method:
click the run method in mian to enter the run method of the SpringApplication class
Here, a space is opened up in the heap to store the created SpringApplication
instance objects, and SpringBootDemoApplication.class
the existing as SpringApplication
the structure The input parameters of the function, and the args in the main method are used as the input parameters of the run method. Then we enter SpringApplication
the constructor.
Start by debugging to see what the values of each attribute are after loading, as shown in Figure 1 below:
Let's focus on a few attributes inside:
A. webApplicationType (web application type)
Enter the WebApplicationType.deduceFromClasspath() method,
you can see it through Debug, here is the last line directly: return WebApplicationType.SERVLET
, that is to say, webApplicationType=SERVLET, as shown in Figure 1 above;
B. Boot registration initializer
I don't know how to say this name, but seeing the last two words, it should be a registration initializer. The next two setXX methods also have a loading initializer. You can see that they both call a common method, but the input parameters are different getSpringFactoriesInstances
.
Enter getSpringFactoriesInstances
the method, and finally SpringFactoriesLoader.loadFactoryNames
call the method SpringFactoriesLoader.loadSpringFactories
. There is a line in this method.
Enumeration<URL> urls = classLoader.getResources("META-INF/spring.factories");
You can see through debug that the loaded URL is
1) spring-beans/5.3.23/spring-beans-5.3.23.jar!/META-INF/spring.factories
2) spring-boot-devtools/2.7.5/spring-boot-devtools-2.7.5.jar!/META-INF/spring.factories
3) spring-boot/2.7.5/spring-boot-2.7.5.jar!/META-INF/spring.factories
4) under the springframework in the maven package spring-boot-autoconfigure/2.7.5/spring-boot-autoconfigure-2.7.5.jar!/META-INF/spring.factories
. In fact, it should be loaded under all packages. META-INF/spring.factories
file, as long as there is BootStrapRegistryInitializer
a class with key=, it will be loaded.
Next, I will use debug to show that
there are 14 properties in the screenshots of the content loaded in the 3 and 4 packages. These properties are in the read file, but there are far more than 14 in this file. A closer look shows that 14 are taken. The first one is placed in Properties. If you don’t believe me, you can also count it. After loading the key-values of the files in the 4 jar packages
and putting them in the result, it’s time to replace the factory implementation in the map. Name (factoryImplementationName is a literal translation), the structure of result is as follows: Then execute the lambda expression inside the method, and put the listener or initializer installed in the ArrayList into the container. When replacing, I found out why the length of implementations is 31. I counted the files in spring-boot and spring-boot-autoconfigurespring-boot-autoconfigure/2.7.5/spring-boot-autoconfigure-2.7.5.jar!/META-INF/spring.factories
spring.factories
Map<String, List<String>> result = new HashMap();
Collections$UnmodifiableRandomAccessList
META-INF/spring.factories
FailureAnalyzer
There are 31 implementation classes in total.
I also read other things and found that the number is not enough. In the process of debugging, I can see that other springframework dependencies are also loaded. For example,
org.springframework.context.ApplicationContextInitializer
there are 8 of them. I found that only the files in spring-boot and spring-boot-autoconfigure were META-INF/spring.factories
found. 7, I still org.springframework.boot.devtools.restart.RestartScopeInitializer
haven’t found it, so I remembered to load it spring-boot-devtools/2.7.5/spring-boot-devtools-2.7.5.jar!/META-INF/spring.factories
, and I found it here:
After the replacement, I saw the Map result displayed on the debug console:
After the replacement, I found that
it was only changed, why should I replace it here? , I didn’t see it very clearly, let’s look down first to find SpringFactoriesLoader.loadFactoryNames
the method of calling this method: we couldn’t find it
in the 4 jar packages , so the property of the constructor of SpringApplication is an empty ArrayList./META-INF/spring.factories
BootstrapRegistryInitializer
this.bootstrapRegistryInitializers
C. Set the initializer
Let's go back to the constructor of SpringApplication and look at the methods of initializers and listeners:
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
I also saw the familiar method, getSpringFactoriesInstances
that is, the content under the 4 jar packages read by the above-mentioned key analysis /META-INF/spring.factories
. We found that there are ApplicationContextInitializer
some , and it has 8 children. This has also been analyzed before, so I won’t go into details here. The screenshot shows the content of debug:
D. Set up the listener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
But ApplicationListener
there are also 10 children, and I won't look for them again where they are in the jar package.
E. this.mainApplicationClass (set application class)
The last step is to infer the main application class.
At this point, the execution process of the constructor is finally analyzed. I really don’t know if I don’t analyze it, and I’m startled when I analyze it. Of course, this is just my own analysis. There are still 2 points in it that I haven’t figured out. I still need to debug a few more times before I can fully understand it. If you really understand it, you won’t forget it for a long time. Only in this way can we internalize it into our own knowledge.
In addition, a mechanism is used here, which is also the Dubbo SPI mechanism mentioned in a blog I wrote before . It is the first to be used in java. Dubbo has been optimized on the basis of java's SPI mechanism.
2.3.2 The second part: the operation of the run method
First look at what is written in this method.
In order to facilitate analysis and understanding, I will use the printed banner information as the dividing point to analyze and see what has been done before and after.
A. Code implementation before printing banner
//1、获取系统纳秒数时间,用于统计执行的总时间
long startTime = System.nanoTime();
//2、创建启动的上下文
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
//3、配置无领导的属性
configureHeadlessProperty();
//4、获取监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
//5、发布启动的事件并由对应的监听器进行处理
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//6、准备环境变量
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
//7、配置忽略的Bean信息
configureIgnoreBeanInfo(environment);
//8、打印Banner信息
Banner printedBanner = printBanner(environment);
Let me first write the general process in the comments, and then I will talk about other operations except 1.
A.2. Create a startup context
Here, bootstrapRegistryInitializers
the pair has been traversed. Obviously, there is no data in it. It has been mentioned in detail in 2.3.1.B here, so I won’t repeat it here. The context of the final startup is DefaultBootstrapContext
that this function is used in step 5.
A.3. Configure the attributes of no leader
In fact, I don’t understand this very well. What is the purpose of setting this, but I still bite the bullet and look down at the attribute values
obtained from System java.awt.headless
, and put the results in. It seems to be like this. Partners can also be confused when they see this~
In Properties.getProperty
the method, true is returned.
I was curious about what this java.awt.headless
is, it looked like a path name, so I found the awt package, and in this package I only saw HeadlessException
the class:
there is a comment on the class:
Raised when code that relies on a keyboard, display, or mouse is called in an environment that does not support it.
What is the purpose of setting this value to true? I saw the correct explanation from an article
: SpringBoot actually wants to set the application to allow it to start even if the display is not detected. For the server, There is no need for a display, so set it like this.
Thanks to this great god for his explanation, I am so enlightened~
A.4. Get the running listener
//4、获取监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
It's a familiar recipe and a familiar taste again, and getSpringFactoriesInstances
the method is here again.
We can easily spring-boot/2.7.5/spring-boot-2.7.5.jar!/META-INF/spring.factories
find SpringApplicationRunListener
the subclass in it, org.springframework.boot.context.event.EventPublishingRunListener
so getSpringFactoriesInstances
the listener returned by the method is also EventPublishingRunListener
, but getRunListeners
the return in the method returns a new created one SpringApplicationRunListeners
, constructs this class, and puts the listener in the constructor.
A.5. Publish the start event and handle it by the application start listener
Then we go down and call SpringApplicationRunListeners.starting
the method.
Entering the starting method calls doWithListeners
the method again. The first parameter in this method is the step name, the second is a functional interface, and the third is the step, the most important of which is the functional interface.
Entering doWithListeners
the method, the method body this.applicationStartup.start(stepName);
finally returns a DefaultStartupStep
.
doWithListeners
Methods SpringApplicationRunListeners
will be used repeatedly in the class, which will be mentioned later.
The method is called later this.listeners.forEach(listenerAction);
, so here listenerAction is the result returned from the method doWithListeners
in the method (listener) -> listener.starting(bootstrapContext)
, and this method is the core method. Due to insufficient internal strength training, I didn’t understand this method at first, so I repeatedly debugged it 5 or 6 times before I understood it. The point that I didn’t understand was that there were 10 elements in the Listener collection. Why were there only 10 elements left after filtering? In the picture below, the serial numbers I underlined are 0, 3, 4, and 5, and the remaining 6 are filtered out.
I found the creation information of these four classes
and the creation information of other classes is as follows:
I will not post the DevToolsLogFactory with the serial number 7, and the corresponding monitoring class is inside this class, so I will not consider it.
The more critical method is starting
the method. In SpringApplicationRunListener
the class, there is a line of comments on this method. Turn it over:
Called immediately when the run method is first started, it can be used for early initialization.
After clicking multicastEvent
in, the most important thing should be getApplicationListeners(event, type)
the method. This method returns the 4 monitoring classes mentioned above, and the method is used to invokeListener
call the method doInvokeListener
. Let's look at this method last.
Remember this multicastEvent method, which will be used in several places later
The method here getApplicationListeners(event, type)
is very important, and the method is finally called retrieveApplicationListeners
. There is a method in the loop in this method, supportsEvent(listener, eventType, sourceType)
which is the method to filter out these 4 listeners.
When supportsEvent(listener, eventType, sourceType)
true is returned, the listener will be added to the collection. This method uses the instanceof keyword to determine whether the previous object belongs to the latter class or its subclass. What does this method mean? There is a line comment on the method:
Determines whether the given listener supports the given event
It can be seen that among the four classes, only other listeners LoggingApplicationListener
are implemented and need to be used for judgment. The last line is connected by &&, and the latter one directly returns True, so let’s look at the previous method. After this method enters the class, this class is implemented , and there is a construction method: this class rewrites method method 10 Most of the classes are the last else directly, that is, the method behind "||". In this method, there is a key point to decide which of the 10 classes will be filtered out. The code is a bit If there are too many, I won’t post too many. If you are interested, you should debug it yourself to learn more. At 2/3 of the method, most classes are filtered out because they return false. The description of this method is: Checks whether the type on the right is assignable to the type on the left. (Explanation: I probably understand this method as whether the generic type of the class implemented when these 10 classes are created is a class or its subclass. I don’t understand it very well. I just explain what I see here. As for the implementation Thoughts, I may need to have more internal skills to understand. In addition, the steps of obtaining the monitor are also the places where I have read the longest time and have the most points that I don’t understand, so I will go here first, and of course I will go further There are places that need to be dug deeper, and interested friends can dig deeper by themselves) Finally, let’s go back to the method to look at the method of calling the listener and call it again : these four classes have re-implemented the method, of course There are 37 classes that write this method, and I won’t list them with screenshots. If you don’t believe me, you can search for it by yourself~ After receiving the event, different implementation classes will process each word according to the event data. According to the aboveGenericApplicationListener
GenericApplicationListenerAdapter.supportsEventType(eventType)
smartListener.supportsEventType(eventType)
GenericApplicationListenerAdapter
GenericApplicationListener
supportsEventType
this.declaredEventType.isAssignableFrom(eventType))
isAssignableFrom
ClassUtils.isAssignable(ourResolved, otherResolved)
ApplicationEvent
invokeListener
invokeListener
doInvokeListener
onApplicationEvent
multicastEvent
After entering, getApplicationListeners(event, type)
the returned listener enters the implementation method of the corresponding listener. For example, in the starting
method here, the passed event class is ApplicationStartingEvent
, so which listener class will handle this event, let’s search globally, or click on onApplicationEvent
the method In the implementation, breakpoints are set in the four we mentioned above, and you will know which listener handles the event when you see which one has entered. I've tried both methods, and they both work fine. We found the listener to RestartApplicationListener
handle ApplicationStartingEvent
the event, as shown in the figure below, the name of the picture is the start event code picture .
If the event is processed, a line of log will be printed on the console, that is, the line with the red line in the figure below.
So where is this line of log printed? Let me show the calling path simply and crudely. You can check it yourself, or search the log keyword globally Created RestartClassLoader
and then push it backwards. Start from the line that draws the blue shadow in the startup event code diagram
onApplicationStartingEvent((ApplicationStartingEvent) event);
: -> Restarter.initialize(args, false, restartInitializer, restartOnInitialize);
-> localInstance.initialize(restartOnInitialize);
-> - immediateRestart();
> -> start(FailureHandler.NONE);
-> Throwable error = doStart();
-> ClassLoader classLoader = new RestartClassLoader(this.applicationClassLoader, urls, updatedFiles);
It is RestartClassLoader
printed in the constructor
(secretly tell you: RestartApplicationListener
there are several events in it, have you found it? We will meet again~)
A.6. Prepare environment variables
After talking about what I thought was the most difficult part, I felt a lot easier.
We're here now and here
prepareEnvironment
's how:
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
// 1、获取或创建环境变量
ConfigurableEnvironment environment = getOrCreateEnvironment();
//2、配置环境变量
configureEnvironment(environment, applicationArguments.getSourceArgs());
//3、配置属性源和环境变量的依附
ConfigurationPropertySources.attach(environment);
//4、监听环境变量
listeners.environmentPrepared(bootstrapContext, environment);
//5、将默认的环境变量移动到最后
DefaultPropertiesPropertySource.moveToEnd(environment);
Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
"Environment prefix cannot be set via properties.");
//6、将环境绑定到SpringApplication
bindToSpringApplication(environment);
//7、转换环境为标准的环境变量
if (!this.isCustomEnvironment) {
EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader());
environment = environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
//8、配置属性源和环境变量的依附(同第三步,不赘述了)
ConfigurationPropertySources.attach(environment);
return environment;
}
A.6.1 Get or create environment variables
ApplicationServletEnvironment
Here, an application service environment variable class is created . This class inherits from StandardServletEnvironment
, this class inherits from StandardEnvironment
, and this class inherits from AbstractEnvironment
. This class is an abstract environment variable class. In this abstract environment variable class, there is a construction method:
enter In customizePropertySources(propertySources);
the method, two more elements are added to the method. After the addition, the environment is as follows: the source of the four elements is also a List, and some of the classes define that this collection is a copy-on-write collection class. Let's take a look at what is in the soure with inde 2 and 3. Here, index=2 is used as an example, which stores system attributes. As can be seen from the figure below, the source stores the basic information of the system, the virtual machine version, and the class version. , operating environment, project directory, etc. The words 3 store the system environment variables, and the keys inside are all uppercase, because we usually configure environment variables in uppercase. Explanation: I won’t expand on the method of obtaining system properties ( ) and system environment variables ( ) here, otherwise it feels endless.
super.customizePropertySources(propertySources);
propertySourceList
public class MutablePropertySources implements PropertySources
propertySourceList
private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
getSystemProperties()
getSystemEnvironment()
A.6.2 Configuring Environment Variables
Then enter the method of configuring environment variables: configureEnvironment(environment, applicationArguments.getSourceArgs());
Then enter configurePropertySources(environment, args);
the method, if the judgment fails, return directly.
A.6.3 Dependency on configuring property sources and environment variables
Going further down is the method of matching property sources and environment variables: ConfigurationPropertySources.attach(environment);
after executing this method, propertySourceList
there will be another family member in the collection:
when you follow the steps below:
- Open
propertySourceList
the element with index=0 in the collection; - open
source
; - Open
source
the inside againsources
;
Repeat the above three steps continuously, repeating this more than ten times, just like infinite nesting dolls, endless, it really seems to be endless, as shown in the picture below. How is
this effect achieved, and why is it designed this way? To be honest, I really don't know the reason for this design, but we can look at the process of achieving this effect. Enter the ConfigurationPropertySources.attach(environment);
method, which is a static method.
The operation here is to pack the 4 attribute source elements in the previous collection as a whole and put them into the newly created object, and ConfigurationPropertySourcesPropertySource
then put the last line in the first element position in the original sources, which is what we said earlier See the effect of infinite nesting dolls.
If you continue to expand according to the three steps mentioned above, you will have the same endless effect. If there are friends who can see this, and know why there is such an infinite set of dolls, please leave a message to advise~
A.6.4 Monitoring environment preparation
Then comes listeners.environmentPrepared(bootstrapContext, environment);
the method, which reuses the method we analyzed above to obtain the listener: the
rewritten multicastEvent
method in the listener is called to publish the event of environment preparation
, so what does the listener that receives this event do? The listener to receive this event is EnvironmentPostProcessorApplicationListener
, and this kind of no-argument constructor has done another thing we are very familiar with: we will know if
we see it, we have analyzed it before, it is the subclass loaded from it, After loading, these are the ones in the figure below: the method of drawing the red line in the above figure is to convert the class of the string obtained from it into a real class, and traverse each class. These classes rewrite the method and process the environment respectively , when traversing : this method has only one line of code, and a static method is called: in this method, the resource class named is added to it, that is , the collection. When the class is traversed, the attribute resource is also placed in the collection Middle: So far, there are 6 attribute resources in the collection: After adding, there are 7 elements in the collection:SpringFactoriesLoader.loadFactoryNames(EnvironmentPostProcessor.class, classLoader));
spring.factories
EnvironmentPostProcessor
spring.factories
postProcessEnvironment
RandomValuePropertySourceEnvironmentPostProcessor
addToEnvironment
random
RandomValuePropertySource
MutablePropertySources
propertySourceList
DevToolsPropertyDefaultsPostProcessor
devtools
propertySourceList
A.6.5 Move default environment variables to the end
The location of this method is as shown in the figure below:
enter the method and finally return false.
A.6.6 Binding the environment to a SpringApplication
The places marked 1 and 2 in the picture are the places that I am more confused. I didn’t understand the one marked 1, and I asserted something, so I let this line go.
Let’s look at the place marked 2. I have debugged this method no less than ten times, all the way into the content of the method. I don’t know where I did it and kept looping because I didn’t find the loop body. The main loop is as shown in the figure below The bind method in the. If someone who knows can give me some advice, I would like to know where I am stuck?
Where does the ConfigurationPropertyName here come from? In the continuous loop process, I found that some keys are the names in the Json file in the figure below, but I don’t know when this file was read.
I'll add it when I figure it out.
A.6.7 Converting the environment to standard environment variables
Then I performed the third step, configuring the attribute source and the attachment of the environment variable ( ConfigurationPropertySources.attach(environment);
)
, I still didn't understand why I had to execute it again, and finally environment
returned the processed one.
PS: There are indeed a lot in it, I still don't understand it, but it doesn't matter, you can know the general logic first, and then take a look at it later. Maybe it will suddenly become clear.
A.7. Bean information ignored by configuration
It can be seen from the implementation of this method that he is configured to ignore BeanInfo, first check whether the value can be obtained from the properties in System spring.beaninfo.ignore
, if not, get it from the environment variable, and set this value in System to True, I don’t understand why this is done here, what is the purpose of setting it to true, why should BeanInfo be ignored, I think it should be used later.
A.8. Print Banner information
In the method in SpringApplication , the bannerMode defaults to CONSOLE, enter the method of the method printBanner
in this method , and then call the printBanner method of the SpringBootBanner method. In the class: Of course, you can also customize the picture or text, and put the custom picture or text in the resources folder. From the following code, it can be seen that it supports custom extensions: so far, the first part of the run method The analysis is over. It really took my life, and it took a lot of time, but I still don't understand why.SpringApplicationBannerPrinter
print
SpringBootBanner
B. Code implementation after printing banner
There are roughly 9 steps here, let's take a look at them one by one:
1. Create the context of the application
2. Prepare the context
3. Update the context
4. Post-processing of the update context
5. Print how long the application took to start and complete
6. Publish the event of the start of the context
7. Call the runner
8. Process the capture 9. Publish
ready events
B.1. Create application context
createApplicationContext();
The annotation for the method is:
Strategy method for creating the ApplicationContext. By default, this method will honor any previous explicitly set application context class or factory and fall back to a suitable default.
ApplicationContextFactory
Entering this method, the abstract create method is called , so which implementation class's create method should we look at?
We entered it and ApplicationContextFactory.DEFAULT
saw that there is a loadFactories method in the for loop.
When you follow SpringFactoriesLoader.loadFactories(ApplicationContextFactory.class, ApplicationContextFactory.class.getClassLoader())
the method all the way down, you can see the SPI in the queried file again , so you can really find its interface implementation META-INF/spring.factories
by searching the full text . ApplicationContextFactory
That is the part of my red box in the picture below.
Then you know which implementation class to go to see the created method.
So, the final context
type is AnnotationConfigServletWebServerApplicationContext
. Then let's take a look at the relationship between
this class and the class returned by the create method. Let's name this picture a context relationship inheritance diagram . From this inheritance diagram, we can see that the top ( ) and the bottom of the red box are drawn The following ( ) relationship, this figure we will also use a method in the preparation context in the next section, let’s look familiar first, then what’s in this, let’s take a look at the constructor: and what is this, let’s take a look Constructor: There is actually a registration method in the last line. What is registered here? Let’s skip here and do further analysis later.ConfigurableApplicationContext
ConfigurableApplicationContext
AnnotationConfigServletWebServerApplicationContext
AnnotationConfigServletWebServerApplicationContext
AnnotatedBeanDefinitionReader
B.2. Prepare context
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
Let me talk about the method of preparing the previous line of the context: context.setApplicationStartup(this.applicationStartup);
look at ApplicationStartup
the translated Chinese comments of the class:
Use StartupStep to detect application startup phases. The core container and its infrastructure components can use
ApplicationStartup
Marker to mark steps during application startup and collect data about the execution context or its processing time.
setApplicationStartup
After clicking on the method, you can see the notes:
Allows the application context to record metrics during runtime.
After going in, it is the method of setting attributes, which will not be repeated here. Then let's take a look at the key method:
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
this method has the most parameters in the started process. It needs to be explained first that there are more processes in this method and the following method of updating the context, so you need to be mentally prepared.
Let's look at this method in several parts. It is still a convention. Unless it is very simple, we will not analyze it, and the others will be analyzed one by one:
B.2.1 Processing context
postProcessApplicationContext(context);
The Chinese interpretation of the method:
This method should be used for any related
ApplicationContext
processing. Subclasses can do additional processing as needed.
It is still debugged many times, and I have not figured out how to translate this method. There are 3 if blocks in the method, but only the setConversionService
method in the last if block goes on. I didn’t understand at first, why do I need to set one like this? Since there is a set, there must be a get. Where is the get used and what conversionService
is it used for?
Enter setConversionService
the method to see a note:
Specifies the Spring 3.0 conversion service used to convert property values, as an alternative to the JavaBeans property editor.
The comment says that this started from Spring 3.0, which is equivalent to an upgrade.
Let's context.getBeanFactory().setConversionService(context.getEnvironment().getConversionService());
break it down,
first look at context.getEnvironment().getConversionService())
the method:
click on getConversionService
the method, we enter ConfigurablePropertyResolver
the class, there is a comment and usage method on the method:
enter the implementation class of this method AbstractPropertyResolver
:
finally return a ConfigurableConversionService
class, and feel the relationship between each class at this time It's a bit complicated, but it's clear when we look at the relationship diagram: In the above diagram, we took
it from the inside , this inheritance , , of these two interfaces, the former is processing conversion ( ), and the latter is converter registration ( method) .ConfigurablePropertyResolver
ConfigurableConversionService
ConfigurableConversionService
ConversionService
ConverterRegistry
<T> T convert(@Nullable Object source, Class<T> targetType);
void addConverter(Converter<?, ?> converter);
Let’s talk about context.getBeanFactory().setConversionService(ConversionService)
the method again, click setConversionService
to enter AbstractBeanFactory
it, and see the set and get methods, so where is the get method called?
As shown in the figure below, you can see which classes have called getConversionService
methods:
From the third call of typeConverter in the figure above, it can be seen that it is related to type conversion. We saw it in the relationship diagram of the previous class, and Conversion means conversion. This is the ConversionService
top-level interface of the conversion service, and there are several implementation classes below it:
I went into StandardBeanExpressionResolver
the class, and you can see it from the name , this class is used to parse beans, and the annotations on this class also have similar instructions:
All beans containing {@code BeanFactory} are provided as predefined variables with their generic bean names, including standard context beans such as "environment", "systemProperties" and "systemEnvironment".
I then followed up and found that the meaning of the naming of some methods is probably related to the analysis of Spring's xml configuration. When using Spring in the early stage, we generally use xml configuration, which will write some String strings, then these strings How is it converted into the corresponding class? I think it is related to this interface. Look at the picture below to understand, including Integer,Double,Boolean
the conversion of basic types, date conversion, etc., each of which has a corresponding interface implementation:
In fact, there are still things that can be said to be dug, but due to the limited space, just click here , leave a homework here: use ConversionService to convert the number of String type to Integer.
After talking about conversionService, let’s talk about the first if block. After judging that it beanNameGenerator
is not empty, it will be put into the cache. When will it be set? There is a method
in , this method is called , but where this method is called, you can’t find it ~ and the following if block is not null, and the following logic is being performed, then where is this set Yes, it is found in the constructor we analyzed in the first part, but this is indeed null by default, which means that there is a set in other places. The same as called in it: there is a method in , this method is called , but where is this method called, I can't find it. I will add it after I understand it here. Of course, if you see this and know why, you still hope to leave footprints and solve my doubts for me~SpringApplication
setBeanNameGenerator
SpringApplicationBuilder
beanNameGenerator
beanNameGenerator
resourceLoader
resourceLoader
beanNameGenerator
SpringApplicationBuilder
SpringApplication
setResourceLoader
SpringApplicationBuilder
resourceLoader
resourceLoader
B.2.2 Initializers are applied to the context
applyInitializers(context);
Chinese comment for this method:
Apply whatever is applied to the context before the context is
ApplicationContextInitializer
refreshed.
After entering this applyInitializers
method, it is a for loop. What is the content of the loop? getInitializers()
The returned result is the 8 initializers in C and the method of setting the initializer that we analyzed in Section 2.3.1 , and these 8 initializers are taken from the spring.factories file. If you don’t remember it, go back and look at it Ha~ Then go down and see the place where the red box is drawn in the picture below. What is the purpose of the code in the red box? In the previous section B.2 , we drew a context relationship inheritance diagram. Do you remember? And this method is to judge whether the context is a generic instance of the class implemented by the traversed initializer , and if so, call the instantiation method of each implementation class. When we finished this method, we found that there were three more elements: why were these three classes put in the application listener, and why were the other five not put in? Let's take a look at the methods implemented by these three classes : the other 5 methods do not call methods , so they will not be placed in the collection of application monitoring classes.
GenericTypeResolver.resolveTypeArgument(initializer.getClass(),ApplicationContextInitializer.class)
ApplicationContextInitializer
initialize
context.applicationListeners
RSocketPortInfoApplicationContextInitializer$Listener
ServerPortInfoApplicationContextInitializer
ConditionEvaluationReportLoggingListener$ConditionEvaluationReportListener
initialize
applicationContext.addApplicationListener;
B.2.3 Publishing context prepared events
listeners.contextPrepared(context);
Now when I see what method is called by the listeners, the instinctive reaction is to publish an event: what happens to
the published event after it is received by which listener? ApplicationContextInitializedEvent
Unfortunately, I didn't find a listener to receive this event. Maybe there are other ways to handle this event? If you have a friend who knows, don’t be reluctant to teach me~
B.2.4 Publishing a Bootstrap Shutdown Event
bootstrapContext.close(context);
The Chinese comment corresponding to this method:
This method is called when
BootstrapContext
the is closed andApplicationContext
ready.
Obviously, another event is released here BootstrapContextClosedEvent
, which is the same as the event prepared by publishing the context in the previous section, but there is still no corresponding listener to handle this event.
B.2.5 Printing the startup log
After these two lines are executed, the two lines of logs in the red box below are printed, indicating that the application is being started, the application name, the jdk version used, the process id, and the project directory. The specific implementation is implemented in the following method, which is relatively simple
. It is the appending of strings and the acquisition of attributes, so I will chatter here.
B.2.6 Bean factory settings
This title sounds a bit like an ice factory. I divided the Bean factory settings into three parts:
1) Application parameter singleton beans and Spring flag singleton beans are added to the cache
2) Whether circular dependencies are allowed and whether the bean definition is allowed to be overwritten
3) Add the Bean factory processor collection to the context
1) Application parameter singleton bean and Spring flag singleton bean are added to the cache.
We click on registerSingleton
the method and enter DefaultListableBeanFactory.registerSingleton
the method. The method called by this method super.registerSingleton(beanName, singletonObject);
is DefaultSingletonBeanRegistry.registerSingleton(beanName, singletonObject)
the method.
Let's name the picture below as Add Singleton Bean Code Picture , and we will use this picture next.
Did you find any problems when you got here? Obviously we only added two beans, why are there four? Where did the other two come from? At this time, we need to take a time shuttle. Let us go back in time to B.2.2 The initializer is applied to the context to analyze applyInitializers(context);
the method, that is, 8 initializers are traversed and initialized, and beanFactory
there are 2 of them after the initializer works. result.
Taking advantage of the heat in the picture above, I want to talk about my heart. The other three of the four caches here registeredSingletons
are Spring’s third-level caches. Let’s not discuss the purpose of this design here. Let’s take a look at these four caches first. The definition of a set:
there must be a reason for this design, don't worry, keep reading. But I want to insert an advertisement first, about the relationship diagram
of two classes: DefaultSingletonBeanRegistry
and : because I was wondering why I clicked and entered . I saw that the inheritance relationship is really long~DefaultListableBeanFactory
DefaultSingletonBeanRegistry
super.registerSingleton(beanName, singletonObject);
DefaultListableBeanFactory
registerSingleton
Advertisement over, back to business, our time machine back to B.2.2 Initializers applied to the context of two initializers: ContextIdApplicationContextInitializer
and ConditionEvaluationReportLoggingListener
.
ContextIdApplicationContextInitializer
This initializer is to set the ID of the application context. It is explained in the comments of this type. Debug directly into the initialization method. Have you seen a familiar method? After entering it, we added the singleton bean code diagram inregisterSingleton
front of it. The process is over, add it to the 2 caches, no more verbosity~~
ConditionEvaluationReportLoggingListener
It’s an old friend again. We have analyzed it in the three listeners in the application context. The comment says that the purpose of this initializer is to write reports to the log, and it is a bean that does not share instances, that is, a singleton bean .
Let’s talk about this value again:autoConfigurationReport
how did it come from? With the for loop, we came toConditionEvaluationReportLoggingListener
the class, and there is a get method in the if block:
After entering from the get method, we liftedautoConfigurationReport
the veil.
When you come here, you will know what to do next. That's right, it is the same logic as the previous one for adding a singleton bean code diagram , no more verbosity.
2) Whether to allow circular dependencies and whether to allow Bean definitions to be overwritten
I feel that there is nothing to say here, both if blocks can be entered, and whether to allow circular dependencies and whether to allow Bean definitions to be overwritten are false by default.
3) Add the Bean factory processor collection to the context and
addBeanFactoryPostProcessor
add line comments on the method:
Adds a new BeanFactoryPostProcessor that will be applied to this application context's internal bean factory before any bean definitions are evaluated. Called during context configuration.
BeanFactoryPostProcessor
In fact, it is to put the context with it BeanFactoryPostProcessor
into the List collection. The collection definition also says that this is used to update the context, which is the method we will analyze in Section B.3 to update the context . Then we will Save it for later.
Pull the line of sight back, if the value is true, what is added is put into the collection lazyInitialization
without context , the default value is false, so do not go inside the if block. When we finished this step, we found that there were three elements in it. When were two of them added? Like the first step, B.2.2 initializer is applied to the context , that is, the method corresponding to the initializer in the method is added:BeanFactoryPostProcessor
lazyInitialization
beanFactoryPostProcessors
applyInitializers(context);
initialize
SharedMetadataReaderFactoryContextInitializer
ConfigurationWarningsApplicationContextInitializer
At this point, the analysis of the growth of the Bean factory has been completed.
B.2.7 Loading resources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
First look at getAllSources
the method. After entering, there is a line comment on this method:
Returns an immutable set of all sources that will be added to the ApplicationContext when {@link #run(String…)} is called. This method combines any primary sources specified in the constructor with any other sources that have been explicitly set @link #setSources(Set) }.
When we analyzed SpringApplication
the constructor, we mentioned that this primarySources
is a set collection with a custom startup class class com.fanhf.demo.SpringBootDemoApplication
. Now put the elements of this collection into a new unmodifiable Set collection and then put it ApplicationContext
in .
Then let's look at load
the method, first go through the breakpoint and look at the results inside
. There are multiple if blocks inside. The method of drawing the red line is the construction BeanDefinitionLoader
, and the inside
getBeanDefinitionRegistry(context)
is to return the register defined by the Bean, as shown in the figure below:
debug go In the first if block, why is the context BeanDefinitionRegistry
an instance? Do you still remember our context inheritance diagram in Section B.1 ? Is a subclass, so the result is True; and constructed , in order to execute the method. One more thing to add here is that there is a line of code in line 4 of this constructor: , we will mention this constructor method below! ! Let's give the above picture a name: call it BeanDefinitionLoader constructor diagram. Finally, let's look at the method: from here we enter the load method: there is a judgment in the second if block , which means checking whether the Bean meets the registration conditions , the result is True, so I won’t talk about the details, and the next step is to register. I seem to have seen this somewhere ~~ Don’t worry, let’s talk about it later. After this method is finished, the load method is also over.AnnotationConfigServletWebServerApplicationContext
BeanDefinitionRegistry
createBeanDefinitionLoader
BeanDefinitionLoader
loader.load();
this.annotatedReader = new AnnotatedBeanDefinitionReader(registry);
loader.load();
isEligible
annotatedReader
AnnotatedBeanDefinitionReader
register
All the way to the doRegisterBean
method, we pay attention to the registration method of the last line:
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
after entering registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
it, there is a line BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
, and beanName="springBootDemoApplication", obviously there is no in the Map, existingDefinition=false, it comes to the else block of else , that is, the picture below, let’s give this picture a name, and we’ll use it next, let’s call it the Bean injection picture . Turning on the time machine and looking back, it has to be said that I have returned . We have mentioned that there is a registration method in the constructor of this class in the BeanDefinitionLoader constructor diagram and B.1, the context inheritance diagram of the context of creating the application , and the registration entry of these 5 beans is in the method. I will list a few here: the last parameter of the method parameters at the red line is the constant value of . Take the first bean as an example to enter the method: this method is called at this time , this is , and the method is an abstract method in it, which implementation class should I enter to implement this method? Let's look at the inheritance relationship diagram again: it is obvious that it will enter the parent class of this class : at this time, it has entered
beanDefinitionNames
AnnotatedBeanDefinitionReader
AnnotationConfigUtils.registerAnnotationConfigProcessors
registerPostProcessor
beanName
registerPostProcessor
registry.registerBeanDefinition(beanName, definition);
registry
AnnotationConfigServletWebServerApplicationContext
registerBeanDefinition
BeanDefinitionRegistry
GenericApplicationContext
DefaultListableBeanFactory
The implementation of this method in the class is the code in the Bean injection diagram .
The other 4 are also the same operation, which explains the source beanDefinitionMap
of beanDefinitionNames
the elements of the sum. At this point, B.2.7 The process of loading resources is basically explained clearly. Of course, there are some details that will not be mentioned. If you are interested, you can read it in detail~
B.2.8 Publishing context loading events and handling events
listeners.contextLoaded(context);
See also listeners
, after entering, you can directly see the corresponding implementation. Inside this method is a for loop, and then publish the event.
Where does the loop data come from? this.application.getListeners()
The listener collection data returned by the method is also ~~ We have analyzed it
in section 2.3.1 - D, setting up listeners
, and finally there are 10 listeners. If you forget it, go back and look at it~ You can see it from the red box in the above picture It turns out that these are the 3 initializers that we have analyzed in B.2.2 when the initializer is applied to the context , plus the 10 listeners obtained in the constructor, a total of 13 are in the context , and an event context
is released together after packaging . ApplicationPreparedEvent
So does this event have a listener to receive and process it? After searching, I found that there are quite a few. Take one of them as an LoggingApplicationListener
example. Here, 3 singleton beans have been added to the bean factory, such as springBootLoggingSystem
, springBootLoggerGroups
and springBootLoggingLifecycle
. Currently, there are 7 special singleton beans in the bean factory, and there is also a RestartApplicationListener
listener, which is also our What has been mentioned repeatedly before will not be analyzed. Let's see what is done after receiving the preparation event here?
So far, the context preparation step has been analyzed. Generally speaking, it is not difficult, but you need to go back and look at it patiently. However, the following update context is a tough nut to crack, so be prepared~
B.3. Update context
refreshContext(context);
After this method enters, it first adds another class applicationListeners
to it : SpringApplicationShutdownHook
, which gracefully closes the hook of the SpringBoot application. Together with B.2.8 of the previous analysis, publishing the context loading event and processing the event , plus this hook, there are currently 14 elements in the application listener collection.
Then enter AbstractApplicationContext.refresh(context);
the method. The content in this method is the best way to write comments so far. I wrote the Chinese comments below the English comments:
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
// Prepare this context for refreshing.
//1、准备更新上下文
prepareRefresh();
//Tell the subclass to refresh the internal bean factory.
//2、告诉子类更新内部的bean工厂
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
//3、准备在此上下文中使用的Bean工厂
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
//4、允许在上下文子类中对Bean工厂进行后处理。
postProcessBeanFactory(beanFactory);
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
// Invoke factory processors registered as beans in the context.
//5、调用在上下文中注册为Bean的工厂处理器
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
//6、注册拦截 Bean 创建的 Bean 处理器
registerBeanPostProcessors(beanFactory);
beanPostProcess.end();
// Initialize message source for this context.
//7、初始化此上下文的消息源
initMessageSource();
// Initialize event multicaster for this context.
//8、为此上下文初始化事件广播
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
//9、初始化特定上下文子类中的其他特殊Bean
onRefresh();
// Check for listener beans and register them.
//10、检查监听器的Beans并注册它们
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
//11、实例化所有剩余(非懒加载初始化)的单例
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
//12、最后一步:发布相应的事件
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.
//重置active的标识
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();
}
}
}
As for the catch block inside, it is relatively simple, so there is no need to analyze it.
B.3.1. Prepare to update the context
prepareRefresh();
Prepares this context for refresh, sets its start date and active id, and performs any initialization of the property source
The first half of the entry method is to set the start date and the active flag to true and log records, which is nothing to say.
We are also divided into three parts here:
1)initPropertySources();
2)getEnvironment().validateRequiredProperties();
3)this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners);
1) initPropertySources();
For this method, the Chinese comment is:
Initializes any placeholder property sources in the context
After entering the method, enter again ConfigurableWebEnvironment.initPropertySources
, and come to StandardServletEnvironment.initPropertySources
the method:
there is only one static method in this method, but the first parameter of the method is a method getPropertySources()
, and what this method returns is MutablePropertySources
, does this class look familiar? We are analyzing run
the printBanner method of the method A.6.1 and A.6.4 of Part A of the first half are mentioned, but when analyzing A.6.1, A.6.3 and A.6.4, they are basically talking about propertySourceList
adding elements to the attributes of this class, a total of 7, Now we WebApplicationContextUtils.initServletPropertySources
can also see the entry method:
the line here looks a bit messy. In fact, since servletContext
and servletConfig
are both null, the two if blocks cannot enter. If they can enter, it is the name pointed to by the blue arrow. The corresponding resource class is replaced with the green underlined class. Unfortunately, the calculation here is wrong, and I didn’t give a chance. As for when the replacement will be made, I think it must be later, and we will talk about it when we meet.
2)getEnvironment().validateRequiredProperties();
Validates that all properties marked as required are resolvable
See ConfigurablePropertyResolver#setRequiredProperties
It means to check whether the necessary attributes are missing. Since requiredProperties
the size=0, no attributes are marked as required, so there is no need to check. If the necessary attributes are missing, an exception that lacks the necessary attributes will be thrown directly. If the source code later If there is no setting in it, it can be customized.
3) this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners);
here is to save the pre-update ApplicationListeners
, we have mentioned in B.3, update context and B.2.8 publish context loading event and process event , if you forget You can go back and look at it~
This part actually does 2 things, which are done before, that is, to prepare all the required attribute resources and all the listeners.
B.3.2. Tell the subclass to update the internal bean factory
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
There are relatively few operations here, GenericApplicationContext.refreshBeanFactory
just an update Bean factory:
the if block of the first entry method is a CAS algorithm, refreshed
the value of the judgment is false, if yes, it is updated to true, in fact, I did not understand the meaning of such judgment and operation, it is If it is not refreshed
changed to true, it means that the operation of updating the context will start from here. Then beanFactory
set serializationId
it to be the same as the context application
. I remember that I have also analyzed this. It is probably applyInitializers(context)
done by an initializer in the method, and the method finally returns beanFactory
.
B.3.3. Bean factories ready to be used in this context
prepareBeanFactory(beanFactory);
There is a line comment on the method:
Configure the factory's standard context features, such as the context's classloader and postprocessor.
beanFactory
Pass the parameters obtained by the previous method prepareBeanFactory(beanFactory);
to this method.
The method is roughly divided into 4 parts to add:
beanFactory.ignoreDependencyInterface
beanFactory.registerResolvableDependency
beanFactory.addBeanPostProcessor
beanFactory.registerSingleton
Here is a list of where these methods correspond to where the elements are placed:
method | collection object | element type | Element example |
---|---|---|---|
addPropertyEditorRegister | propertyEditorRegistrars | Set< PropertyEditorRegistrar> | PropertyEditorRegistrar |
addBeanPostProcessor | beanPostProcessors | List< BeanPostProcessor> | ApplicationContextAwareProcessor,ApplicationListenerDetector |
ignoreDependencyInterface | ignoredDependencyInterfaces | Set<Class<?>> | EnvironmentAware.class、ApplicationEventPublisherAware.class、pplicationContextAware.class |
registerResolvableDependency | resolvableDependencies | Map<Class<?>, Object> | (BeanFactory.class, beanFactory)、(ApplicationContext.class, AnnotationConfigServletWebServerApplicationContext) |
registerSingleton | registeredSingletons | Set< String> | “environment”,“systemProperties” |
registerSingleton | singletonObjects | Map<String, Object> | (“environment”,ApplicationServletEnvironment)、(“systemProperties”,Properties) |
It is easy to guess what it means according to the literal meaning. Take the last one registeredSingletons
as an example, index=0 to 3 has been analyzed in the setting section of the B.2.6 Bean factory , and index=4 to 6 is to publish the context loading event in B.2.8 and The event processing section has been analyzed, and here is a supplementary singleton object with index=7 to 10. The final result is the 11 elements in the figure below: the others are not
listed one by one. If you are interested, you can debug and take a look inside. element.
B.3.4. Allow post-processing of bean factories in context subclasses
postProcessBeanFactory(beanFactory);
This method is an abstract method implemented by multiple subclasses, so which implementation class should we enter? When we analyze this B.3 and update the context , refreshContext(context);
there is a context in the method, and the type of this context is: AnnotationConfigServletWebServerApplicationContext
:
Let’s look at the inheritance relationship diagram again :
after refreshContext(context);
entering the method, go all the way down, and you will reach ConfigurableApplicationContext.refresh
the method, here The real implementation of the method is in AbstractApplicationContext
, and there are two subclasses ( ReactiveWebServerApplicationContext
and ServletWebServerApplicationContext
) that also rewrite the refresh method, but they all call it, super.refresh();
so they still come to AbstractApplicationContext
it. The method analyzed in this section postProcessBeanFactory(beanFactory);
is rewritten by multiple implementation classes, and the current AbstractApplicationContext
inside The object of the object actually SpringApplication
calls refresh
the method in it AnnotationConfigServletWebServerApplicationContext
. If you don’t believe me, let’s look at the debug situation:
the this object is AnnotationConfigServletWebServerApplicationContext
, so we enter the implementation of this class, but this class is lazy and calls the method of the parent class: super.postProcessBeanFactory(beanFactory);
in this Through the breakpoint in the method, we can see that the following two if blocks are both false, so we only need to analyze super.postProcessBeanFactory(beanFactory);
the method. As can be seen from the inheritance diagram
above , the parent class of this class is , and here is the real implementation of the processing bean factory: the first 2 lines in this method and the previous section B.3.3, prepare in this context The two methods of adding elements in the table in the Bean factory used are the same. The key is to look at the blue shadow breakpointServletWebServerApplicationContext
registerWebApplicationScopes();
Method:
The two lines before and after this method are very simple, so I won’t talk about it. Focus on the static method in the second line of the method:WebApplicationContextUtils.registerWebApplicationScopes(getBeanFactory());
After entering, there is a line comment on the method:
Registers a web-specific scope ("request", "session", "global-session") with the given BeanFactory to be used by the WebApplicationContext
registerWebApplicationScopes(beanFactory, null);
This static method has only one line of code, and the method is called
, as shown in the figure below: After breakpoint debugging, it is found that only the one in the red box will be executed, the two if blocks are false, and the one in the red box is registered as the comment says of the web application context. The first red box is the registration scope, one is Request, and the other is Session. The elements registered to resolve the dependency collection are all Servlet-related requests, responses, and Session object factory classes.
So this method mainly deals with the registration of the factory of the Web application.
B.3.5. Call the factory processor registered as a Bean in the context
invokeBeanFactoryPostProcessors(beanFactory);
This method and the following
registerBeanPostProcessors(beanFactory);
methods are basically things in Spring, so I will pick up important analysis.
After the breakpoint enters this method, it comes to the first line of the method. The second parameter of the method's input parameter is a method, and the method returns a List collection. There are 3 elements in getBeanFactoryPostProcessors
the collection . These three elements are in B. 2.6 The settings of the Bean factory have been analyzed emphatically. After we enter the method, the current instance is entered into this large if block, which is roughly divided into 5 parts,beanFactoryPostProcessors
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
beanFactory
BeanDefinitionRegistry
1)
beanFactoryPostProcessors
Traversing the pair, placeBeanDefinitionRegistryPostProcessor
the instance of yes andBeanDefinitionRegistryPostProcessor
the instance of not respectively.
2) First, call the implementationPriorityOrdered
ofBeanDefinitionRegistryPostProcessor
3) Next, call the implementationOrdered
ofBeanDefinitionRegistryPostProcessor
.
4) Finally, all others are calledBeanDefinitionRegistryPostProcessor
until no other handlers are present.
5) Finally at the end,postProcessBeanFactory
the callbacks .
Let's talk about it separately:
1) beanFactoryPostProcessors
Traverse the pair, and place the BeanDefinitionRegistryPostProcessor
instances that are yes and those that are not.BeanDefinitionRegistryPostProcessor
After the for loop is executed, there is a line of comments:
Do not initialize FactoryBeans here: we need all regular uninitialized beans to have the beanFactory postprocessor applied to them! Separate the implemented
PriorityOrdered, Ordered
ones from the restBeanDefinitionRegistryPostProcessors
This line of comments is for 2, 3, 4, let's look down:
2) First, call the PriorityOrdered
implementationBeanDefinitionRegistryPostProcessor
Let's look at postProcessorNames
the final result first, and then analyze getBeanNamesForType
, this method will be used many times in the future ~
click getBeanNamesForType
, we Entering DefaultListableBeanFactory.getBeanNamesForType()
the method, because configurationFrozen
the initial value is false, so before the value is changed to true, we directly enter the if block: we enter the
method doGetBeanNamesForType
, first create a result
List collection, and then have two for loops , let's talk about the first for loop first, this is for beanDefinitionNames
traversal, and the debug shows that there are 7 elements in it.
Where do these 7 elements come from? We have analyzed the source of the elements in the last Bean injection diagram in B.2.7 Loading resources , so I won’t go into details here. Then we enter the method first, and the set of elements entered in the last line of the method is also analyzed in B.2.7 loading resources as we just said . The difference between and in is that the beanName and the corresponding class are placed, and only the beanName is placed. Then I will focus on putting elements in the middle, and the two Boolean values that hinder the placement of elements: and , first look at :beanDefinitionMap
beanDefinitionNames
getMergedLocalBeanDefinition
return getMergedBeanDefinition(beanName, getBeanDefinition(beanName));
getBeanDefinition(beanName)
beanDefinitionMap
beanDefinitionMap
beanDefinitionNames
beanDefinitionMap
beanDefinitionNames
result
isFactoryBean
matchFound
isFactoryBean
It can be seen from the above figure FactoryBean.class.isAssignableFrom(beanType)
that it is to judge ConfigurationClassPostProcessor
whether to inherit FactoryBean
, and the method here isAssignableFrom
is a native method, and the annotation on this method explains the purpose of this method.
So the value here is false, so we isFactoryBean
can enter the if block: then we have to judge the value. The corresponding method is that there are a lot of codes in this method. I haven’t read all of them, but I will come to the end of this method in the end. One line: After entering this method, there are still many method bodies. I directly hit the breakpoint at the return, so that I can directly know where to jump out, and finally came to: In this if block, the if judgment is a three-eye operation , get arrived, I will write like this in the future. . . We enter the method: here to judge whether it is inherited , we look at the definition of the class in the figure below, it is obviously true, so the value of is also true , so finally the element is put into the collection . And the other 6 are not so lucky, they all use the else block of this method, so they are all false. Let’s talk about the second for loop. The collection of the loop is that there are 11 elements in it. These 11 elements are exactly the same as the elements we mentioned in B.3.3 , the Bean factory that is going to be used in this context . In fact, it is in What is added here is that we did not analyze it at the time. Then iterate over:
matchFound
isTypeMatch(beanName, type, allowFactoryBeanInit);
return typeToMatch.isAssignableFrom(predictedType);
ClassUtils.isAssignable(ourResolved, otherResolved)
ConfigurationClassPostProcessor
BeanDefinitionRegistryPostProcessor
ConfigurationClassPostProcessor
matchFound
result
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
matchFound
manualSingletonNames
registeredSingletons
manualSingletonNames
This is also isTypeMatch(beanName, type)
the judgment made by the use. The 11 elements are all internalConfigurationAnnotationProcessor
passed by the first for loop except beanName=, so there is what we said at the beginning of this section: postProcessorNames
there is only one element in it internalConfigurationAnnotationProcessor
.
Then postProcessorNames
it traversed again, because there is only one element in this collection, it is much faster:
in the if block, currentRegistryProcessors
put internalConfigurationAnnotationProcessor
the corresponding class of beanName=, and
processedBeans
put the beanName of this string. currentRegistryProcessors.add
The input parameter of the method is to get the corresponding Bean according to the beanName. There is a method. If beanFactory.getBean
the implementation class corresponding to this method cannot get the method that the Bean will call, I will not continue to talk about it, otherwise there are really too many.AbstractBeanFactory.doGetBean
createBean
After the for loop, it is sorted and added ConfigurationClassPostProcessor
to registryProcessors
it, and then there is another method that needs to be analyzed. This method will be called repeatedly below, that is, invokeBeanDefinitionRegistryPostProcessors
we enter this method:
as soon as the method comes in, it is a for loop. The key point It is the method at the blue breakpoint. This is an abstract method. Of course, we are entering the ConfigurationClassPostProcessor
sub-method of rewriting:
the front of this rewriting method is basically judgment, and we directly enter the last line of the method, which is the inside of the method with the red box :
The above figure is processConfigBeanDefinitions
a method of a do-while loop at 2/3 of the method, and the method I draw a red frame is worth analyzing. These two methods make the elements of the sum at the break point in the above figure surge beanDefinitionMap
by beanDefinitionNames
7 to 142 ways. The method of the first red box parser.parse(candidates);
is to read two properties files: classify the 861 properties of these two files to configure the properties, that is , the 47 elements in the collection in the
previous figure , each of which configClass
Some of the elements have multiple elements. Because the parse method has too many nests, I won’t go into details here. If you are interested, you can break in and check, but you need to be patient, otherwise you will be confused. Circled. Here I will simply talk about this.reader.loadBeanDefinitions(configClasses);
the processing process of the method. After entering this method, the second line is a for loop:
in the method pointed by the arrow in the above figure, there are two methods of drawing red boxes. After entering the first one, you can find this line of code: the
this.registry.registerBeanDefinition(definitionHolder.getBeanName(), definitionHolder.getBeanDefinition());
red in the second for loop After entering the box method, you can find this method:
this.registry.registerBeanDefinition(beanName, beanDefToRegister);
the two methods corresponding to these two methods this.registry
are DefaultListableBeanFactory
the methods in the class: the above picture is the place where the elements are
registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
actually added to beanDefinitionMap
and . So far the second stepbeanDefinitionNames
2) First of all PriorityOrdered
,BeanDefinitionRegistryPostProcessor
the analysis of the implementation of the call is almost completed.
3) Next, call the implementation Ordered
of BeanDefinitionRegistryPostProcessor
.
We found that this place is almost exactly the same as the code in the second step, but we can see from the comments that the second step is to find the implementedPriorityOrdered
processor, and this step is to find the implementedOrdered
post-processor.
ObviouslypostProcessorNames
the value is still the same, the corresponding class is stillConfigurationClassPostProcessor
, this class is not implemented,Ordered
so the result of the following sorting and other operations is equal to the result of the second step.
But pay attention toinvokeBeanDefinitionRegistryPostProcessors
the first input parameter of the method:currentRegistryProcessors
, which is cleared at the end of the first step, so the for loop will exit directly after the method enters.
4) Finally, all others are called BeanDefinitionRegistryPostProcessor
until no other handlers are present.
This while loop is actually equivalent to doing nothing. The if judgment drawn in the red box in the figure below is obviously false, and operations such as sorting are cleared in the first stepcurrentRegistryProcessors
, and no new elements are put in in the second step. And it was also cleared after the second step, so it cannot continue to execute.
5) Finally at the end, postProcessBeanFactory
the callbacks .
According to the debug results in the above figure,registryProcessors
there are 3 elements,regularPostProcessors
and one element is used asinvokeBeanFactoryPostProcessors
the first parameter of the method. ForregistryProcessors
the first two elements in the loop, becauseBeanFactoryPostProcessor.postProcessBeanFactory
the method has not been implemented, only we have analyzed it before. TheConfigurationClassPostProcessor
only way to implementpostProcessBeanFactory
:
After entering the method of drawing the red line in the above figure, we directly entered enhanceConfigurationClasses(beanFactory);
the method:
in the above figure (because we wanted to put the code in a picture, we folded the if block in the first for loop) is the first for After the loop, in the end, only our custom startup class com.fanhf.demo.SpringBootDemoApplication
passed the if judgment pointed by the yellow arrow, and was put into the green underlined configBeanDefs
Map collection, and our custom startup class was strengthened during the second for loop. Then assign the value to beanClass
, which is the breakpoint of the blue shadow.
The regularPostProcessors
processor corresponding to an element in the collection is a SpringApplicatioon
static inner class PropertySourceOrderingBeanFactoryPostProcessor
in the implementation postProcessBeanFactory
method:
we seem to have seen this static inner class somewhere, and add the Bean factory processor collection in part 3 of the B.2.6 Bean factory settings section It has been analyzed in the contextpostProcessBeanFactory
, then this method:
and this moveToEnd
method seems to have been seen somewhere, yes, it is exactly the same method in A.6.5 Move the default environment variable to the end .
After talking about invokeBeanFactoryPostProcessors
the first half of the method, it is the second half. The organization is still very clear: pick out the corresponding beanName corresponding
to the implemented bean from 142 beanNames to traverse: there are 6 elements to traverse, the first Because the first element is directly skipped, the others are put into the immobile List collection in turn according to the conditions met. After putting it into the collection, since the bean corresponding to the beanName is put into the contained collection during the for loop in the figure above , and the sum is completed in the for loop in the figure below, I don’t understand why it doesn’t match The same processing, why bother to convert to the corresponding bean when the cycle is performed again. After converting to the corresponding bean, each method is called , and each post-processor processes it separately. In the end, only the processor inside can do some work. As for what it does, I won’t talk about it first, and I will analyze it later when I use it. The processors of the other two collections are useless and can't do anything.BeanFactoryPostProcessor
processedBeans
priorityOrderedPostProcessors
BeanFactoryPostProcessor
orderedPostProcessorNames
nonOrderedPostProcessorNames
priorityOrderedPostProcessors
invokeBeanFactoryPostProcessors
priorityOrderedPostProcessors
Let's go back to the initial method of this section invokeBeanFactoryPostProcessors
. The above is the analysis of the first static method of this method, followed by an if block. There are 3 conditions in this if condition for "and" judgment, the first two They are all true, but the last one is to judge beanDefinitionMap
whether there is one in it loadTimeVeaver
. Unfortunately, it is not, so the operation in if cannot be carried out.
So far, this invokeBeanFactoryPostProcessors(beanFactory);
method has finally been roughly analyzed. Since it is basically the contents of the spring component (beans, context, core) package, I have never seen it before, so this method is really painful when I first look at it, because I am walking. Don't know where you are? Fortunately, I calmed down later, and after a little bit of reading, it gradually became clear.
B.3.6. Registering the Bean Processor Created by the Interception Bean
registerBeanPostProcessors(beanFactory);
After entering this method, it is a static method, that is, the red box in the figure below, and the horizontal line is what we have spent a lot of time analyzing in B.3.5, calling the factory processor registered as a Bean in the context. Does it look a little familiar?
With the foundation of B.3.5 , the analysis of this method will become much simpler. The implementation of this method is basically the same as the static method in the previous section, that is, the lower half of the method drawn with a horizontal line in the figure above. Select the corresponding beanName corresponding to the implemented bean from the 143 beanNames for traversal, and finally find 6, then put these 6 into the corresponding collection according to different conditions, and then convert the beanName to the corresponding bean
class BeanPostProcessor
Put it into the corresponding processor set:
when traversing orderedPostProcessorNames
and nonOrderedPostProcessorNames
traversing, if the corresponding Bean processor is MergedBeanDefinitionPostProcessor
an instance, it will also be placed internalPostProcessors
in it, including priorityOrderedPostProcessors
, these four processor sets will be registerBeanPostProcessors
baptized with methods, Except nonOrderedPostProcessors
the other three will be sorted again.
So registerBeanPostProcessors
what is done in the method?
In fact, it is to put all 6 processors in these 4 sets beanPostProcessors
into it. Let's first look at what elements are in it before
executing the first method: there are already 5 in it, the first 4 are in B.3.3, and we are going to be in As mentioned in the table of the Bean factory used in this context , the last one is put in where we draw the red line in the picture below. After we execute the last method, how many elements are there?registerBeanPostProcessors
registerBeanPostProcessors
There are 11 elements, and after the code in the red box in the above picture is executed, there will be 12 elements.
At this point, registerBeanPostProcessors(beanFactory);
the analysis is over, and finally back to refresh
this part of the method:
the two methods of drawing the green frame are what we analyzed in B.3.5 and B.3.6 , which is this section.
B.3.7. Initialize the message source of this context
initMessageSource();
Initialize the message source, if not defined in the context, use the parent class.
From our analysis and the situation of the breakpoint, it is indeed not defined, so the source of the proxy is used, and then injected into the singleton bean factory.
B.3.8. Initialize event broadcast for this context
initApplicationEventMulticaster();
Initializes the application event broadcaster, used if not defined in the context
SimpleApplicationEventMulticaster
Looking at the comments, it feels the same as the previous method: initializing the source of this context is the same operation.
As the comment says, there is indeed no bean factory applicationEventMulticaster
, so use it SimpleApplicationEventMulticaster
, and then register this in the singleton bean factory SimpleApplicationEventMulticaster
.
B.3.9. Initialize other special beans in specific context subclasses
onRefresh();
Analysis in this section : I only select part of the source code for analysis, mainly based on the logs printed on the console. Other interested parties can analyze breakpoints by themselves~
We all know that Springboot comes with Tomcat, and the default port of Tomcat is 8080, so where is this 8080 set? I will reveal the answer in this section.
AfteronRefresh();
entering, we then entercreateWebServer();
:
the fifth line method in this method:getWebServerFactory();
after entering, the method is also usedgetBeanNamesForType
, and the bean that inherits the class is found from 143 beansServletWebServerFactory
, and there is only onetomcatServletWebServerFactory
classTomcatServletWebServerFactory
, so we enter This class is alsogetWebServer
the method of drawing a red frame. This is an abstract method. We enter the rewritten methodTomcatServletWebServerFactory
:
getWebServer
the fourth line of the method createsthe attribute value and attributeTomcat
defined in the Tomcat class.hostname
port;
in this method, there are other methods worth looking at:prepareContext(tomcat.getHost(), initializers);
, there are many things in this method, which are some operations before the initialization of Tomcat, I will not write here, otherwise there are too many, interested can go in and have a look.
Let’s go back to port 8080. What we finally use is not defined in the Tomcat class, butcustomizeConnector
in the method drawn in the red box in the above figure:in
this methoddefined in the classin the figure belowThen we come tothe last line of the method:enter this method, and then come tothe constructor:Math.max(getPort(), 0);
getPort()
AbstractConfigurableWebServerFactory
getWebServer
return getTomcatWebServer(tomcat);
TomcatWebServer
There is one in the constructor. After initialize();
entering a line of log in this method will be printed to the console, and the port in the log in the console is put in there as we analyzed earlier.
After executing initialize
the first line of code of the method in the above figure, the console will print out the following log. Students who see this may think, we can configure server.port in the application.properties file or xml file, so how do we get this? Just leave a homework here, analyze and check it yourself.
The whole initialize
method body is a synchronization method, adding a built-in synchronizer, and there is a line of code in the synchronizer: , this.tomcat.start();
the start();
method has two implementation classes, we enter LifecycleBase
the method of the class, we enter this synchronization method, how many in this method The branch and the state are constantly changing. When I first looked at this, I was confused. Fortunately, I walked a few more times later and gradually understood. I was looking at the enumeration value by chance and found that there was a state change on the class Lifecycle
. Note, maybe after reading it, you can be less confused.
There is a text description below this state change, take it yourself to check it~
There is a method in the try-catch block startInternal
, this method is rewritten by many classes, and it keeps going down, and finally comes to the method StandardService
in the class startInternal
:
we see the broken After clicking the blue shaded part in the above picture, a line of log is printed:
so where does this line of log Starting service [Tomcat]
come from? The answer lies in the key in the sm.getString method:
followed by the method StandardEngine
of the class startInternal
:
key is standardEngine.start
, and the corresponding value is as shown in the figure below: Starting Servlet engine: [{0}]
, the value in {0} is replaced with the data in the red box in the picture above
The log printed to the console is as follows:
Where does the version number of Tomcat come from? We startInternal()
saw it in the log.info in the method ServerInfo.getServerInfo())
, so the version number must be obtained by this line of code ServerInfo
. There is a static block:
there is a file with a hard-coded path in the block: /org/apache/catalina/util/ServerInfo.properties
when we enter the file, we see the information in the figure below:
it just corresponds to the Tomcat version number in the log; Apache Tomcat/9.0.68
after walking here, I am really stunned I don’t know how many times I cycled between which states. Finally, my patience was exhausted, and I came directly to the place where the log in the picture below is printed: the last line in the picture below is the place where the log above is printed: don’t
be
confused The printing time of my log, yes, I analyzed it yesterday morning, and continued to analyze it after work today, so the time of the log is much different. There are still some places that I don't understand. Let's take a look at it later when I have time. In the above method, another line of log will be printed after the last line:
After some process, the state changes in different states as a cycle, and this cycle has come several times, and has gone through some processes, this onRefresh The method is the end. I feel that I will walk step by step. I will probably have to walk for 24 hours to complete the walk. I will blow it up and analyze it later~~
B.3.10. Check the Beans of the listener and register them
registerListeners();
relax! ! ! ! This method is much simpler!
Divided into three parts:
1) Put the listener back in its new home. Traverse the 14 listeners
we mentioned in B.3, Update Context section and put them in another collection
. 2) Re-find the name of the listener bean Home
This for loop has a comment on it:
Don't initialize FactoryBeans here: we need to leave all regular beans uninitialized for post-processors to apply to them!
This calls the method we analyzed in B.3.5, calling the factory processor registered as a bean in the context and B.3.6, registering the bean processor created by intercepting the bean , and picking out the listener getBeanNamesForType
that implements the class from all beanNames ApplicationListener
bean name, a total of 6 and put them in the new collection.
3) I want to release early application events, but I think too much.
From the point of view of the breakpoint, because earlyApplicationEvents
size=0, the if block cannot enter, and it is over~
B.3.11. Instantiate all remaining (non-lazy-loaded initialization) singletons
finishBeanFactoryInitialization(beanFactory);
Completes the initialization of the bean factory for this context, initializing all remaining singleton beans
To be honest, after a few times of debugging, I found that there configurationFrozen
seemed to be a lot of code except for changing (whether the Bean definition metadata can be cached for all Beans) from false to true, but in the end it did nothing because it was filtered.
1) Initialize the conversion service for this context.
Because the beanFactory does not contain it conversionService
, it cannot be set.
2) Register the default embedded value parser, which is mainly used for parsing in attribute values.
Because embeddedValueResolvers
there is already a parser in it, so there is no need to go down.
In fact, the elements in this embeddedValueResolvers
collection should be analyzed in B.3.5, calling the factory processor section registered as a Bean in the context , but because it exceeds the scope of Springboot, it is skipped, and sure enough, it will come out sooner or later. invokeBeanFactoryPostProcessors(beanFactory);
I have to pay it back. The method is called
in PostProcessorRegistrationDelegate
the class . This method is an abstract method. Find the rewriting method in the implementation class , and then call the method in this method . This method is also an abstract method. We enter the method of the class , and the last line in this method enters The last line of this method is to add elements to it . There are a lot of calling processes, so I won’t take screenshots one by one. Just look at the source code, I believe you can find it if you are smart~ 3) Initializing LoadTimeWeaverAware is also a bit dramatic. The first method is also in ourinvokeBeanFactoryPostProcessors( Collection<? extends BeanFactoryPostProcessor> postProcessors, ConfigurableListableBeanFactory beanFactory)
postProcessor.postProcessBeanFactory(beanFactory);
PropertyResourceConfigurer
postProcessBeanFactory
processProperties(beanFactory, mergedProps);
PropertyPlaceholderConfigurer
processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props)
doProcessProperties(beanFactoryToProcess, valueResolver);
beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
embeddedValueResolvers
B.3.5. Invoking the factory processor section registered as a Bean in the invokeBeanFactoryPostProcessors(beanFactory);
context is analyzed in the section, and the matching beanName beanDefinitionNames
is selected from the 143 beanDefinitions in the collection . The result is embarrassing, and there is none. 4) Allows caching of all Bean definition metadata without expecting further changes. Here is a practical thing I said at the beginning in this method. The changed value is true, and this value is called in the method in our figure above. . 5) Instantiate all remaining (non-lazy-initialized) singletons. This method is considered to have the most method body among the five places, mainly because it traverses the 143 beanDefinitions in the collection twice, one time creates and initializes all non-inert singleton beans in the 143 beans, and the other time is A post-initialization callback is fired for all applicable beans. The first for loop is finally the called method, and this method should also be analyzed in section B.3.5, calling the factory processor registered as a Bean in the context . In the second for loop, only the beanName is the corresponding Bean: , which is the subclass that is eligible to enter the if block, and the debug is indeed entered: and then enters the method, but because the condition is not satisfied, nothing happens. did not do.LoadTimeWeaverAware
finishBeanFactoryInitialization(beanFactory);
configurationFrozen
beanFactory.getBeanNamesForType
beanFactory.preInstantiateSingletons();
beanDefinitionNames
getBean(beanName);
org.springframework.context.event.internalEventListenerProcessor
EventListenerMethodProcessor
SmartInitializingSingleton
smartSingleton.afterSingletonsInstantiated();
B.3.12, the last step: release the corresponding event
finishRefresh();
Complete the refresh of this context, call the lifecycle handler's onRefresh() method and publish
ContextRefreshedEvent
This method is divided into 5 methods:
1) clears resource cache
2) initializes lifecycle handlers for this context
3) broadcasts updates for lifecycle handlers
4) publishes final event
5) participates in LiveBeansView MBean (if active).
What is worth mentioning here is only 2, 3, 4, 1 will not be mentioned, it is to clear the collection, and the if judgment of 5 NativeDetector.inNativeImage()
is in B.3.3, and the Bean factory that is going to be used in this context also has this judgment. At that time, there was no Analyze and check the effect yourself.
2) Initialize the life cycle processor of this context
initLifecycleProcessor();
. According to the debug, it is found that the bean factory lifecycleProcessor
exists and is defined. If there is no definition, it is like the hint in the comment to go to the else branch. However, no matter whether there is a definition, the final life cycle processor Both are DefaultLifecycleProcessor
.
3) Broadcast update for the life cycle processor
getLifecycleProcessor().onRefresh();
This method should be the most complicated method among the 5 methods.getLifecycleProcessor()
What we return is the initialization we mentioned aboveDefaultLifecycleProcessor
, and call the method in this classonRefresh()
:after
this method is executedstartBeans
It is to set the running state to true, we enterstartBean
the method, first callgetLifecycleBeans();
the method:
after this method enters, it also calls what we mentioned in the previous section in the third linebeanFactory.getBeanNamesForType();
, this time the main type isLifecycle
, and finally only found There are 4 inheritedLifecycle
beanNames, and there is an if judgment in the code above tolifecycleProcessor
filter out the beanName, so there are 3 left, namely the three key-value elements shown in the beans in the figure below .
After this method is over, we will come backstartBeans
. There are not many codes here, but I did spend some time understanding one of the methods in it,phases.computeIfAbsent()
that is, the way of writing . The blog written by the guy , after running the examples, I realized that this method does save a lot of code. If you are not familiar with it, you can read and run the examples to understand it well. Here is a homework to come again. Let's play~
After the final forEach method is over,phases
put 3 key-value pairs in the Map corresponding to the variable, that is, thephases
object at the break point in the above figure. The type of this object isMap<Integer, LifecycleGroup>
, andLifecycleGroup
the value inside isDefaultLifecycleProcessor
an internal class of , as shown in the figure below, so the method at the breakpoint of the blue shadeadd
isLifecycleGroup
the method of the class.
whenforEach
After the method ends, the value of the Map of phases is traversed, the method is executed LifecycleGroup
, start
and start
the method calls the method in the for loop doStart
. The first thing to traverse is that LoggingApplicationListener
as soon as the method comes up, the spirngBootLoggingLifecycle
cache of the beanName is deleted, and then the method is called in the try-catch LoggingApplicationListener
, start
and this type of method and the corresponding bean start
to be looped for the third time : the method, All just change the state of the from false to true. When the cycle arrives , it is different, and enters the method of the class : the method in the above figure is to call the method, and the value of is that the constructor of this class is called in the method, that is, we are in B.3.9, initialization Other special beans in specific context subclasses should be analyzed, but not elaborated. Then we come to the method: after executing the blue shaded breakpoint in the above figure, the console prints the log in the figure below. 4) Publish the final event Publish the event, then only the following 4 classes will receive and process this event. For example , and , roughly clear the corresponding cache. So far B.3, update the contextwebServerGracefulShutdown
WebServerGracefulShutdownLifecycle
start
running
webServerStartStop
WebServerStartStopLifecycle
start
start
this.webServer.start();
webServer
TomcatWebServer
WebServerStartStopLifecycle
createWebServer
TomcatWebServer
start
publishEvent(new ContextRefreshedEvent(this));
ContextRefreshedEvent
ClearCachesApplicationListener
SharedMetadataReaderFactoryBean
The basic analysis is completed. Of course, there are some things that are not mentioned in this article. You can check the breakpoints by yourself~
B.4. Post-processing of update context
It's very simple here, because this method is currently empty, and users can implement it themselves.
B.5. How long did it take for the printing application to start up?
After executing this line of code, you can see a sentence printed on the console:
After reading getStartedMessage
the appended string in the method, it is clear.
B.6. Publish the event of context start
listeners.started(context, timeTakenToStartup);
We seem to see old friends again. Entering the started method, we can see that SpringApplicationRunListeners
a large number of methods in this class are called in the class
private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction, Consumer<StartupStep> stepAction)
.
Enter listener.stared
the method and see that there is a line comment that means:
The context has been refreshed and the application has started, but
CommandLineRunner
theApplicationRunner
application runner has not yet been called.
Enter the called SpringApplicationRunListeners.started
method:
It is found that this method has no method body and is marked as @Deprecated
a default
method. Something feels wrong, why didn't it come true? At that time, I didn't understand it. After repeated debugging, I set a breakpoint in the publish method, and after chasing the caller from publish, I realized that the real implementation should be EventPublishingRunListener
implemented in it, because SpringApplicationRunListeners
there is only one implementation class. @Deprecated
Why didn't I go directly to this method of the implementation class from the unmarked started
method? I should drill down instinctively. Careless.
In the method EventPublishingRunListener
inside started
, you can see that the event is published.
After the event is released, it finally enters the method AbstractApplicationContext
inside publishEvent
to release the time.
Entering this method, multicastEvent
you can see the method we have analyzed before:
here is to broadcast this event to the 4 listeners we analyzed before, and the 4 listeners will judge whether they are capable of handling this event.
So ApplicationStartedEvent
which listener handles the event? I tried to search globally but couldn't find a listener for this event. So here I don't quite understand what is the use of events that are not processed by listeners after they are published? Leave a homework!
In addition, add a sentence: the method here and the method in section B.9started
below are all through calling the method, then calling the method, and then broadcasting the event, and we are directly calling the method in the method analyzed in section A.5 . I actually I don't quite understand why and the method have to go a long way. There must be a reason for existence, and if you look at it later, you may understand.ready
context.publishEvent
publishEvent
multicastEvent
listener.starting
multicastEvent
started
ready
B.7. Invoking the Runner
callRunners(context, applicationArguments);
ApplicationRunner
Since there is no and in the context CommandLineRunner
, the runners collection is empty, and the method ends directly when it reaches for. ApplicationRunner
So how can we get the sum from the context CommandLineRunner
? The answer is to customize the implementation class to implement the run method of one or two of these two interfaces.
B.8. Handle caught running exceptions
The run method in the figure below is the second part of our analysis. There are two try-catch in it, and there is a method to handle running exceptions in the catch block. Let's analyze whether there is something in this method.
Enter handleRunFailure
the method:
only these 4 places can be analyzed, and the last line directly throws an exception. I won't analyze it.
B.8.1, Coding for Handling Exit
handleExitCode
An event was published ExitCodeEvent
, but I still can't find a listener for this event.
B.8.2. Publish failed events to listeners
Entering the failed method, we can see that this method calls the doWithListeners
method again. This method calls the method of drawing the red line callFailedListener
. There is a method in the try block in this method listener.failed(context, exception);
. Are you familiar with it?
The implementation of this failed method is still It is in EventPublishingRunListener
the class.
In the if-else here, regardless of whether the context is empty or active, it is guaranteed that the failed event can be broadcasted. But obviously, if the if branch is taken, the active attribute here is set to true prepareRefresh
when the update context we analyzed earlier is executed this.active.set(true);
. If it is false, it means that there is a bug before the update context is reached. Take the else branch. So which listener is this ApplicationFailedEvent
event received and processed? I searched this event globally, and I am an old friend again RestartApplicationListener
:
this method goes all the way down, and you reach the revome method. This method is to delete the context from the collection, and this collection, I remember I mentioned it before, is all written time. A copied collection, but the function is different. The aforementioned is for attribute resources, and this one is for context:private final List<ConfigurableApplicationContext> rootContexts = new CopyOnWriteArrayList<>();
B.8.3. Making a failure report
reportFailure(getExceptionReporters(context), exception);
According to the name of the method, it can be seen that this error was reported, how was it reported, and what did it do? Let’s look at the method first getExceptionReporters(context)
:
Well, it’s a familiar formula and a familiar taste. We searched directly globally, and SpringBootExceptionReporter
sure enough, we found its implementation class under spring-boot-2.7.5.jar FailureAnalyzers
:
so getExceptionReporters
the input parameters of the method are FailureAnalyzers
, that is, there is only one element in the collection in the figure below exceptionReporters
, which is the failure analyzer FailureAnalyzers
, then the for loop will only loop once and call reporter.reportException(failure)
the method .
From the method of drawing the red line, we have arrived at the site. Let’s look at the methods FailureAnalyzers
of this type : from then on, we enter the interface . There is only one method in this interface: , enter the implementation class of this method, and we come to this interface The abstract implementation class of this abstract implementation class found that there are actually many analyzers, and each analyzer performs customized processing of error messages. I won't give an example here. Those who are interested can check it out for themselves. When the class is returned , it is used as the input parameter of the method: when you see the method in the first line of the method, you can search instinctively , and you can find the implementation class in the package: let’s take a look at the implementation of report by the log record error analysis reporter: here It is to print the error information to the log file. In addition, I also found the log when the spring-boot failed to start before. It turns out that I learned from here and unveiled the mystery.reportException
analyze
FailureAnalyzer
analyze
AbstractFailureAnalyzer
analyze
FailureAnalysis
report
loadFactories
FailureAnalysisReporter
springt-boot-2.7.5.jar/META-INF/spring.factories
LoggingFailureAnalysisReporter
B.8.4. Delete the registered context
shutdownHook.deregisterFailedApplicationContext(context);
When the program reaches the end of creation and ConfigurableApplicationContext
an exception occurs, then this method of unregistering the application context will be executed.
This method is SpringApplicationShutdownHook
implemented in a class, and there is a line of annotations on this class:
A Runnable to use as addShutdownHook to perform a graceful shutdown of a Spring Boot application. This hook keeps track of registered application contexts and any operations registered via SpringApplication#getShutdownHandlers.
Let's look at the implementation of the method:
in fact, the application context object in the set collection is deleted.
B.9. Publish ready events
listeners.ready(context, timeTakenToReady);
This method is similar to the started method in Section B.6, enter listener.ready(context, timeTaken))
the method, you can see the note:
Called immediately before the run method completes, when the application context has been refreshed,
CommandLineRunners
and all sumsApplicationRunners
have been called.
Enter EventPublishingRunListener
the ready method inside, and the operation of the started method is the same, but the event class is changed.
After publishing the event, the corresponding listener will proceed to the next step after receiving the event. The following figure is the listening class that processes the received events RestartApplicationListener
. Seeing the picture below is not very familiar, because I also took a screenshot of this picture in section A.5 , but the analysis was ApplicationStartingEvent
the event at that time.
So far, Springboot has been started, and our treasure hunt is over~
3. Homework
According to what I don’t understand during the analysis process, I will leave a homework for myself, and I will read it again later, and then make up:
1) Use ConversionService
the completed homework to convert the number of String type to Integer: Link
2) application.properties
File or xml file How to get the configuration server.port
in the program?
3) ApplicationStartedEvent
Which listener will handle the event after it is published?
4) computeIfAbsent
Corresponding demo, completed homework: link
4. Expansion
In fact, there are some places where you can expand and learn, such as the following, I feel that they can be used in future work
1) Custom banner
2) Event release demo
3) Custom error report
4) Execute custom run method
V. Summary
From reading the source code, to debugging over and over again, to understand from front to back, I found that the startup process of Springboot uses a lot of SPI and event publishing. In these two ways, I usually use event publishing many times to decouple the business. It is a very good way to deal with it. I didn’t know where the idea of event handling encapsulated by our big guys came from. Now I probably found the source, so I feel painful and happy in the process of reading the source code. The painful point is Sometimes I don’t know the reason for this implementation. After reading it more than ten times, I still don’t understand how to get to a certain line of code. This article also explains what I don’t understand; the happy point is that I finally know how some logs are. It has appeared, what excellent ideas are used in it, and what points can be expanded, it feels very enlightening.
Of course, there are still places that I don’t understand, and the understanding of some places may need to be corrected, so if you have any questions during the reading process, please leave a message for advice~
This analysis of the SpringBoot source code, from the beginning of the analysis to the completion of the blog today, it took almost a month and a half intermittently. The time is very long, but I think it should open a door to the cultivation of my inner strength. Source code, I can use the method of analyzing source code this time to analyze other source codes. In short, after reading it, I really have a lot of gains, and I will continue to work hard in the future! ! !