Spring core circular dependency

What is a circular dependency?

Circular dependency: To put it bluntly, there is a direct or indirect dependency relationship between one or more object instances. This dependency relationship constitutes a circular call.

The first situation: you rely on your own direct dependence

Insert image description here
Second case: direct dependence between two objects
Insert image description here
Third case: indirect dependence between multiple objects

Insert image description here

The direct circular dependencies in the first two cases are relatively intuitive and easy to identify, but the third indirect circular dependency is sometimes difficult to identify because the business code call level is very deep.

N scenarios of circular dependencies

Circular dependencies in spring mainly occur in the following scenarios:

Insert image description here

Singleton setter injection

This injection method should be the most commonly used by spring. The code is as follows:

Insert image description here

This is a classic circular dependency, but it can run normally. Thanks to spring's internal mechanism, we can't perceive that there is a problem with it at all, because spring silently solves it for us.

There is a three-level cache inside spring:

  • singletonObjectsThe first-level cache is used to save bean instances that have been instantiated, injected, and initialized.

  • earlySingletonObjectsSecond level cache, used to save bean instances that have been instantiated

  • singletonFactoriesThe third-level cache is used to save the bean creation factory so that later extensions can have the opportunity to create proxy objects.

The following picture shows you how spring solves circular dependencies:

Insert image description here
Careful friends may find that the second level cache is of little use in this scenario.

Then the question is, why use the second level cache?
Just imagine, if the following situation occurs, how should we deal with it?

Insert image description here

TestService1dependenceTestService2TestService3,ButTestService2dependencyTestService1,at the same time TestService3也由赖于TestService1.

Follow the process in the above figure to injectTestService1 into TestService2, and the instance of TestService1 is from the third level Obtained from cache.

Assuming that the second level cache is not used, the process of injecting TestService1 into TestService3 is as follows:

Insert image description here

TestService1 is injected intoTestService3 and the instance needs to be obtained from the third-level cache, and what is stored in the third-level cache is not the real instance object, but ObjectFactoryObject. To put it bluntly, the two times obtained from the third-level cache are ObjectFactory objects, and the instance objects created through it may be different each time.

Isn't this a problem?

In order to solve this problem, spring introduced the second level cache. In Figure 1 above, in fact, the instance of the TestService1 object has been added to the second-level cache, and when TestService1 is injected into TestService3 , just get the object from the second-level cache.

Insert image description here

There is another question, why do we need to add ObjectFactory objects to the third-level cache? Can't we just save the instance objects directly?
答:不行,因为假如你想对添加到三级缓存中的实例对象进行增强,直接用实例对象是行不通的。

How does spring handle this scenario?

The answer lies in this code in the AbstractAutowireCapableBeanFactory classdoCreateBean method:
Insert image description here
It defines an anonymous inner Class, the proxy object is obtained through the getEarlyBeanReference method. In fact, the bottom layer generates the proxy object through the of the AbstractAutoProxyCreator class. getEarlyBeanReference

Multiple instance setter injection

This injection method occasionally occurs, especially in multi-threaded scenarios. The specific code is as follows:

Insert image description here
Many people say that in this case, an error will be reported when starting the spring container. This is actually wrong. I am very responsible to tell you that the program can start normally.

why?

actually tells us the answer in the method of the AbstractApplicationContext class, which will call the method, which The purpose is to initialize some beans in advance when the spring container starts. The method is called internally in this method.refreshfinishBeanFactoryInitializationpreInstantiateSingletons

Insert image description here

The red marked area can be clearly seen:非抽象、单例 并且非懒加载的类才能被提前初始bean.

Multiple instances are classes of type SCOPE_PROTOTYPE, which are not singletons and will not initialize beans in advance, so the program can start normally.

How to let him initialize the bean in advance?

Just define a singleton class and inject TestService1 into it

Insert image description here
Restart the program and the execution results are:

Insert image description here

注意:这种循环依赖问题是无法解决的,因为它没有用缓存,每次都会生成一个新对象。

constructor injection

This injection method is now very rarely used, but we still need to understand it and look at the following code:

Insert image description here
Running results:
Insert image description here
A circular dependency appears. Why?
Insert image description here
It can be seen from the process in the figure that the constructor injection failed to be added to the third-level cache, and the cache was not used, so the circular dependency problem could not be solved.

Singleton proxy object setter injection

This injection method is actually quite common. For example, in scenarios where you usually use: @Async annotation, a proxy object will be automatically generated through AOP.

This is also the case with my colleague.

Insert image description here
I learned from the previous article that an error will be reported when starting the program, and a circular dependency will appear:

org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'testService1': Bean with name 'testService1' has been injected into other beans [testService2] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.

Why are there circular dependencies?

Insert image description here
To put it bluntly, after the bean initialization is completed, there is another step to check: whether the second-level cache and the original object are equal. Since it is not important to the previous process, it is omitted from the previous flow chart, but here is the key point, let us focus on it:

Insert image description here
The colleague's problem happened to be when he got to this code and found that the second-level cache and the original object were not equal, so a circular dependency exception was thrown.

If you change the name of TestService1 at this time to: TestService6, everything else remains unchanged.

Insert image description here
Restart the program and it's magically fine.

what? Why is this?

This starts with the bean loading order of spring. By default, spring searches recursively according to the complete path of the file, sorting by path + file name, and the first one is loaded first. So TestService1 is loaded before TestService2, and after changing the file name, TestService2 is loaded before TestService6.

Why is there no problem if TestService2 is loaded before TestService6?

Insert image description here
In this case, the second-level cache in testService6 is actually empty and does not need to be judged from the original object, so cyclic dependencies will not be thrown.

DependsOn circular dependencies

There is also a somewhat special scenario. For example, we need to instantiate Bean B before instantiating Bean A. In this case, we can use the @DependsOn annotation.
Insert image description here
After the program is started, the execution result is:

Insert image description here
In this example, there would be no problem if neither TestService1 nor TestService2 added the @DependsOn annotation. However, if this annotation is added, a circular dependency problem will occur.

Why is this?

The answer is in this code in the method of theAbstractBeanFactory class:doGetBean

Insert image description here
It will check whether the dependsOn instance has a circular dependency, and throw an exception if there is a circular dependency.

How to solve circular dependencies?

If there is a circular dependency problem in the project, it means that the circular dependency cannot be solved by spring by default. It depends on the print log of the project to see what kind of circular dependency it belongs to. Currently the following situations are included:

Insert image description here
Circular dependencies generated by generating proxy objects

There are many solutions to this type of circular dependency problem, the main ones are:

  • Use@Lazy annotation, lazy loading
  • Use@DependsOn annotation to specify the loading sequence
  • Modify the file name and change the loading order of circularly dependent classes

Circular dependencies generated using @DependsOn

This type of circular dependency problem can be solved by finding the @DependsOn annotation where the circular dependency is, forcing it not to have circular dependencies.

Multiple instance circular dependencies
This type of circular dependency problem can be solved by changing the bean to a singleton.

Constructor circular dependency
This type of circular dependency problem can be solved by using the @Lazy annotation.

Guess you like

Origin blog.csdn.net/GoodburghCottage/article/details/126933724