Simple analysis of Spring source code

foreword

During the interview some time ago, I was asked about the source code of Spring. The question was actually not too deep, but since it has been a long time since I saw the source code of Spring last time, I almost forgot about it, so I basically didn’t answer it.

In order to consolidate and at the same time understand some issues that were not clear before, I have this article. The full text is long, but overall it is not difficult to understand. It is to analyze some common problems from a macro perspective:
Spring creation process , bean life cycle , bean creation process , beanDefinition creation process , Spring Boot startup process and some small point .

Content reference:

  1. Spring detailed source code analysis: https://www.bilibili.com/video/BV1T54y1n7PB/?p=77
  2. IOC and AOP profile analysis: https://www.bilibili.com/video/BV1584y1r7n6

Spring's creation process

The first thing to be clear is that each Spring application we create is mainly divided into two types of interfaces, BeanFactoryand ApplicationContextthe latter inherits the former and has more functions than the former, such as AOP.

In earlier versions of Spring, using the XmlBeanFactory was a way of creating a Spring container.
XmlBeanFactory implements the BeanFactory interface, which can parse the Bean definition from the XML configuration file
and initialize the Bean object. In this version, Spring does not directly integrate AOP functions, but can
integrate third-party AOP frameworks, such as AspectJ or JBoss AOP, through the Spring AOP module.

However, with the continuous upgrade and update of Spring version, XmlBeanFactory is abandoned and
replaced by ApplicationContext. ApplicationContext inherits BeanFactory interface and
provides more functional support for users, including native support for AOP. Therefore, if you want to use
Spring AOP, it is recommended to use ApplicationContext as your Spring container instead of the outdated
XmlBeanFactory.

For details, please refer to the Java Guide written above.

The implementation class of these two interfaces is equivalent to a factory, and the factory pattern is used here . It’s just that this factory has many complex functions, which also shows that one of the benefits of the factory model is that if you centrally manage object creation, you can add some additional functions uniformly.

Among them, Spring manages objects that require singletons ConcurrentHashMapto ensure thread safety. At the same time, when creating a singleton object, sychronizedlocks are also used. For details, refer to org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingletonthe method.

bean life cycle

The bean life cycle is mainly divided into three stages : initialization, use, and destruction . And can be controlled by the following methods:

  1. Instantiation : When the Spring container starts, it will instantiate all the beans configured in the configuration file or annotation through the reflection mechanism. This is the initial stage of the bean life cycle.

  2. Attribute assignment : After instantiation, Spring will assign values ​​to Bean attributes according to the attribute values ​​specified in the configuration file or annotation. This is the second phase of the bean life cycle.

  3. Custom initialization method : When the attribute assignment is completed, Spring will call the custom initialization method, which can be realized by implementing the InitializingBean interface or specifying the init-method method in the configuration file or annotation. This is the third phase of the bean life cycle.

  4. Use : After the initialization method is executed, the Bean can be used by the application. At this stage, the Bean can receive various requests and calls to serve the application.

  5. Custom destroy method : When the application ends, the Spring container calls the bean's custom destroy method. This can be achieved by implementing the DisposableBean interface or specifying the destroy-method method in a configuration file or annotation. This is the final phase of the bean life cycle.

bean creation process

First of all, through the creation of the breakpoint Spring container, we can know that org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitializationthe bean is created in this method.

All the way to the breakpoint, you can come to org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletonsthe method. We can see that this method is based on beanNamethe search beanDefinitionto determine whether the bean exists.

Here is to continuously traverse beanDefinitionNamesthe collection to create beans. Note that the collection is Lista type, indicating that the order is preserved, that is, the creation of beans here is strictly in accordance with the order.

In addition, if the bean is abstract, or non-singleton, or lazy loaded, it will not be initialized. One thing worth noting here is that only such beans are initialized together when the beanFactory is created, and other types of beans are not like this.

Finally we come to org.springframework.beans.factory.support.AbstractBeanFactory#doGetBeanmethods. This method controls the creation and acquisition of the bean, that is, this method is used to obtain the bean. If it cannot be obtained, it will call different situations for processing, call for org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeaninitialization, and then return the bean. It is worth noting that before the call createBean(), the bean construction dependency will be prepared first, and the error of the circular dependency is also generated at this time.

The above mentioned createBean()is the core method of creating beans, which is the whole process before and after bean creation. If you create a bean, you will come to org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBeanthis method, as shown below:

doCreateBean()The bean creation process can be divided into three parts: constructing objects createBeanInstance(), filling properties populateBean(), initializing instancesinitializeBean() , and registering and destroyingregisterDisposableBeanIfNecessary() . See below:

The object construction stage is simply to create an object .

@AutowireThe stage of filling attributes is to inject the attributes marked with annotations . The third-level cache is used here. Remember what I mentioned before, doGetBean()here it will be called first getSingleton(), and this search will be searched in the third-level cache step by step, as follows picture:

If allowEarlyReferenceit is true, then the singletonFactory just added will be executed, and the AOP operation has been completed. Similarly, if a circular dependency occurs when the object is created and the properties are filled, it doesn't matter at this time, because it will be looked up in the third-level cache.

The initialization instance stage is mainly to call various hook functions we have written , such as the implemented BeanNameWareinterface and the implemented initializingBeaninterface. AOP is also executed at this time. There is no need to worry about doing AOP again after doing AOP in the third-level cache. Spring will put the beans that have done AOP into the collection to judge that the beans have been AOPed.

The registration and destruction phase is to register the beans that implement the destruction interface .

For details on the third-level cache, please refer to this article: Circular dependencies and solutions in Spring .

The above doGetBean()piece of code also explains why if it is a circular dependency of the constructor, it cannot be solved (because at least the object must be initialized before it will be added to the cache, pay attention to the location of execution), if it is, it cannot be solved addSingletonFactory( prototypebecause addSingletonFactoryThe precondition is that this bean is a singleton pattern).

The creation process of beanDefinition

For Spring applications, according to ApplicationContextthe type of creation ( ClassPathXmlApplication, FileSystemXmlApplication, XmlWebApplicationContext ), different strategies are used to read the xml file to complete the creation of beanDefinition.

First of all, we must clarify an idea. As mentioned above, the core of Spring IOC is beanFactory. In the process of our creation ApplicationContext, it will be called org.springframework.context.support.AbstractApplicationContext#refresh, and this method completes the creation of beanFactory.

In this method, obtainFreshBeanFactory()a beanFactory is first created. The method will obtainFreshBeanFactory()be called org.springframework.context.support.AbstractXmlApplicationContext#loadBeanDefinitionsto complete the creation of beanDefinition. Here we will create one XmlBeanDefinitionReaderto read the location of the xml file passed in when we start the program, parse the tag and encapsulate it into a BeanDefinitiontype object, and store it in the beanFactory beanDefinitionMap(also ConcurrentHashMapa type). BeanDefinitionIt is equivalent to bean information, and beans are created by reflecting these information.

The processing mentioned here is based on ClassPathXmlApplication. Different ApplicationContextimplementation classes have slightly different ways of obtaining beanDefinition.

After the beanFactory is created, refresh()it will be called again finishBeanFactoryInitialization()to complete the creation of the singleton non-lazy-loaded bean. As we said above, some beans are created during the beanFactory creation process.

The startup process of Spring Boot

Spring Boot startup is divided into four stages: service construction, environment preparation, container creation, and container filling .

For details, please refer to the second link above.

Any Spring Boot project needs to run SpringApplication.run()(note here is SpringApplicationthe static of the class run()) to start. This method will first create a SpringApplicationtype object. During the creation of this object, many necessary preparations for configuring the ApplicationContext will be completed (such as obtaining the current system configuration information, judging the web application type according to the class file in the classpath, printing the banner image, etc.).

Then call SpringApplicationthe type object just created run():

This method will complete the creation of the container (ApplicationContext). Among them createApplicationContext(), an ApplicationContext will be initialized, and the information just obtained will be combined. At the same time, prepareContext()the beanDefinitionMap will be initialized, and then refreshContext()the relevant operations of the beanFactory will be called.

It is worth noting that refreshContext()the relevant operations of beanFactory are performed here, just like in Spring, and will also come to org.springframework.context.support.AbstractApplicationContext#refresh:

It is also called to obtainFreshBeanFactory()create a beanFactory, but this method does not do anything at this time, because Spring Boot is used ServletWebServerApplicationContextas a container, the beanFactory has been created, and the call obtainFreshBeanFactory()will not actually do anything at this time.

onRefresh()In addition, the startup of the Tomcat server is completed in this method .

For refresh()a detailed introduction to each method called in the method, please refer to the video: https://www.bilibili.com/video/BV1hv4y1z7PQ.

In addition, the scanning of beanDefinition in Spring Boot is different from the method mentioned above . Spring Boot already has some basic beanDefinitions at the beginning, such as the startup class of our own program, and then it invokeBeanFactoryPostProcessors()executes various post-processors of beanFactory by calling. The most important thing is ConfigurationClassPostProcessorthat it scans all configuration classes and uses ConfigurationClassParserTake to scan them .

It implements BeanFactoryPostProcessorthe interface postProcessBeanFactory(). You can see that this method will be called processConfigBeanDefinitions()to process it. Here it will be called to ConfigurationClassUtils.checkConfigurationClassCandidate()judge whether it is a configuration class or not. You can track the source code of this method to know that the configuration class mentioned here does not necessarily have to exist @Configuration. @Component, @ComponentScan, @Import, @ImportResourcecan all be processed.

ConfigurationClassParserThe method will be called later parse()to process the previously scanned configuration class. This method will eventually come doProcessConfigurationClass()to recursively process all configuration classes (the configuration class here does not only refer to @Configurationsome , but any @Componentclass that is directly or indirectly marked).

For the above short paragraph about the automatic configuration process of Spring Boot , please refer to the video for details: https://www.bilibili.com/video/BV1NY411P7VX

So far, the main four steps of Spring Boot startup have been completed.

some small points

How does Spring find the corresponding bean according to the type?

There is a map collection in Spring's beanFactory allBeanNamesByType, the key is Classthe type, stores the dependent type, and the value String[]stores all singleton beans and non-singleton bean names (id).

How is FactoryBean stored in Spring?

If you configure a bean's static or non-static factory method (factory-method) through an xml file, then the principle is not difficult to figure out. The main discussion here is how Spring handles when using interfaces FactoryBean.

First of all, for beanDefinition, the class that implements FactoryBeanand injects into the container is the same as other classes injected into the container, only one beanDefinition will be generated, and the type of this beanDefinition is the type of the class, not the getObject()type of the object ( ) to be generated .

So it stands to reason that Spring can only get FactoryBeanthe type of bean, how does it get the object it generates?

After the above push, we know that the bean is finally obtained through doGetBean()the acquisition. Here, after the bean is obtained, it will still be called getObjectForBeanInstance(), as shown in the following figure:

This method will not be expanded, it is roughly to judge whether to obtain the bean or the factory of the bean this time (note that the name and beanName are passed in here). If you want to get the factoryBean, then just return it directly, because the factory bean (factoryBean) is stored in the beanDefinitionMap at first. If you want to get the object generated by the factory, then call the factory method to create the object according to the situation, because if it is determined that the object is a singleton pattern, Spring will cache it in beanFactory,factoryBeanObjectCache so that the object created once is directly fetched here That's it.

Note that the objects produced by the factory bean are not stored in singletonObjects.

Why @AutoConfigurationYou Can Delay Registration

We know that configuration classes always precede auto-configuration classes, and here's why. The specific principle is a bit convoluted, and it will be explained in several steps here.

Register auto-configuration classes

First, let me introduce how to register the automatic configuration class. Unlike ordinary configuration classes, which can be annotated, the registration of automatic configuration classes will be different.

Before Spring Boot 2.7.0,@AutoConfiguration we should create a folder in the project path META-INF/spring.factoriesand configure it as follows in the file :

It doesn’t matter if you forget the format here, just look at a jar with automatic configuration function, you will have this file, just imitate the format.

After Spring Boot 2.7.0, @AutoConfigurationsometimes , we should create a folder in the project path META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports, and then configure it in the file as follows:

Here you only need to write the fully qualified class name of the configuration class.

Then remember to add @Configuration/ to the class @AutoConfiguration.

In addition, after 3.0, only the second method can be used to declare the automatic configuration class.

Configuration class and automatic configuration class do not refer to whether they exist @AutoConfiguration(after all, this annotation is only available after Spring Boot 2.7.0, but the concept of automatic configuration class has long existed), but to distinguish from the registration method.

The automatic configuration class has many more functions, such as arranging the order, which makes it more convenient for us to use@ConditionalOn... this type of annotation , because this type of annotation is judged immediately, that is, whether it meets the conditions immediately when loading, so it is very sensitive to the loading order.

For example, both beanA and beanB are under the path, beanA is only injected when beanB is injected into the container, but if beanA is injected first due to improper sequence arrangement, and beanB has not been injected at this time, beanA injection will fail.

Rely on @AutoConfigureOrder, we can change the initialization order of the configuration class, note that it is different @Order, only the AOP order and the automatic assembly order during bean injection can be modified (for details, please refer to the blog: The wrong use of the Bean loading order to dispel rumors , which will not be introduced here).

In addition @AutoConfigureBefore, etc., you can also adjust the loading order of the automatic configuration class, and these annotations are unified by @AutoConfigurationone annotation (all become one of its attributes), and it is also worth noting that the default is @AutoConfigurationto proxyBeanMethodsimprove falsethe startup speed of Spring Boot .

How to scan auto-configuration classes

We registered the automatic configuration class above, so how does Spring Boot scan it?

In fact, many blogs have mentioned this point, which is the annotation on our Spring Boot startup class @SpringBootApplication, which is included in this annotation @EnableAutoConfiguration, and this annotation is also included @Import(AutoConfigurationImportSelector.class).

Among them, the class is introduced here AutoConfigurationImportSelector, this is one ImportSelector, it will scan the beanDefinition , so selectImports()you can find out what's going on by looking at the class directly, I won't expand it here, and finally use it ClassLoaderto getResources()scan the two files (because Before 3.0, spring.factoriesit is still compatible and can be used together).

So when recursively parsing the startup class of the Spring Boot project (mentioned above ConfigurationClassParser), when the startup class is parsed @Import, it will be found AutoConfigurationImportSelector, thereby introducing the automatic configuration class we registered.

How to ensure that the scanning of the automatic configuration class must be after the configuration class

From the above analysis, we know how the automatic configuration class is scanned, but only based on the above, there is no guarantee that the automatic configuration class will be scanned after the configuration class, but now based on the above knowledge, this can also be done It was figured out.

The above mentioned AutoConfigurationImportSelectoris not only one ImportSelector, but more importantly, it implements DeferredImportSelectorthe interface, which is inherited ImportSelector, and this is the core point.

Do you still remember that it is used ConfigurationClassParserto parse()recursively scan beanDefinition? In this method, at the end of the scan , the implementation class obtained in the previous scan will be called , and the execution will be postponed:DeferredImportSelector

In fact, just look DeferredImportSelectorat the comments, which are also clearly stated above. This ImportSelectorwill be executed after scanning all configuration classes. The above source code is only a direct proof of its guarantee of this behavior.

But I don’t know if you still have a question, that is, if we declare a configuration class in the same path where we start the class, and then spring.factoriesregister the class in , then the behavior of this class is considered a configuration class, or is it automatic configuration class?

The answer to this question is actually auto-configuration classes. Looking ConfigurationClassParserat parse()the source code, we will find that it will actually judge whether it is an automatic configuration class first org.springframework.boot.autoconfigure.AutoConfigurationExcludeFilter#getAutoConfigurations, and if it is, it will not be loaded now.

Guess you like

Origin blog.csdn.net/weixin_55658418/article/details/131216667