What is circular dependency
What is the circular dependency? It can be divided into two parts:
and
cycle refers to the cycle in the computer field, and the execution flow forms a closed loop; depends on as the usual prerequisite to complete this action, and we usually say The meaning of dependence on is broadly the same.
Spring
Bean
, there is a direct or indirect dependency between one or more 060f2b1388b6de instances, forming a circular call. The circular dependency can be divided into direct circular dependency and
indirect circular dependency, a simple dependency scenario of direct circular dependency:
Bean A
dependent on Bean B
, then Bean B
turn depends on Bean A
( Bean A -> Bean B -> Bean A
), an indirect cycle dependent scene dependency: Bean A
dependent on Bean B
, Bean B
dependent on Bean C
, Bean C
dependent on Bean A
, more than the middle layer, but eventually formed Loop ( Bean A -> Bean B -> Bean C -> Bean A
).
Types of circular dependencies
The first is self-reliance , which relies on itself to form a circular dependency. Normally, this circular dependency does not occur because it is easy to be discovered by us.
The second is that directly depends on , which occurs between two objects, such as: Bean A
depends on Bean B
, and then Bean B
in turn depends on Bean A
. If you are more careful, it is not difficult to detect with the naked eye.
The third is indirectly dependent , this type of scenario occurs dependent objects that depend on three or more, and indirectly dependent simplest scenario: Bean A
dependent on Bean B
, Bean B
dependent on Bean C
, Bean C
dependent on Bean A
, when imagine When there are many intermediate dependencies, it is difficult to find this kind of circular dependency, and it is usually checked with the help of some tools.
Spring's support for several cyclic dependency scenarios
Before introducing Spring's handling of several cyclic dependency scenarios, let's take a look at the cyclic dependency scenarios in Spring. Most common scenarios are summarized as shown in the following figure:
There is a saying that there are no secrets under the source code. Let's explore whether these scenes are supported by the source code, and the reasons for support or non-support. Let's not say much,
Spring
The first scenario-setter injection of singleton bean
This method of use is also one of the most commonly used methods. Suppose there are two Service
, OrderService
(order-related business logic) and TradeService
(transaction-related business logic), the code is as follows:
/**
* @author mghio
* @since 2021-07-17
*/
@Service
public class OrderService {
@Autowired
private TradeService tradeService;
public void testCreateOrder() {
// omit business logic ...
}
}
/**
* @author mghio
* @since 2021-07-17
*/
@Service
public class TradeService {
@Autowired
private OrderService orderService;
public void testCreateTrade() {
// omit business logic ...
}
}
In this kind of cyclic dependency scenario, the program can run normally. From the code point of view, there is indeed a cyclic dependency. That is to say, Spring
supports this cyclic dependency scenario. Here we are not aware that the reason for the cyclic dependency is that Spring
has been silent. Solved it.
Assuming that no processing is done and executed according to the normal creation logic, the process is like this: the container first creates OrderService
, finds that it depends on TradeService
, then creates OrderService
, and finds that it depends on TradeService
..., an infinite loop occurs, and finally occurs A stack overflow error, the program stops. In order to support this common cyclic dependency scenario, Spring
divides the creation of objects into the following steps:
- Instantiate a new object (in the heap), but have not yet assigned values to the object properties
- Assign a value to the object
- Call some methods of the implementation class of
BeanPostProcessor
Bean
has been created and the attribute assignment is complete. At this time, allBeanPostProcessor
interface in the container will be called (egAOP
) - Initialization (if
InitializingBean
is implemented, the method of this class will be called to complete the initialization of the class) - Return the created instance
For this purpose, Spring
introduced to address this issue three cache (cache three defined org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
), the first-level cache singletonObjects
for storing fully initialized good Bean
, taken from the cache Bean
may be used as the second The level cache earlySingletonObjects
Bean
objects exposed in advance, and the original 060f2b1388b9f1 object (attributes have not been assigned) is used to solve the circular dependency. The third level cache singletonFactories
used to store the cache of the singleton object factory and stores the Bean
factory Object, used to resolve circular dependencies. The processing flow of the above example using the three-level cache is as follows:
If you have read the definition source code of the third-level cache, you may also have this question: why the third-level cache should be defined as Map<String, ObjectFactory<?>>
, can't you directly cache the object? The object instance cannot be saved directly here, because then it cannot be enhanced. For details, see the source code of the method of org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
The second scenario-setter injection of multiple instances of Bean
This method is usually used relatively little, or use the two Service
previous article as an example. The only difference is that it is now declared as more than . The sample code is as follows:
/**
* @author mghio
* @since 2021-07-17
*/
@Service
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class OrderService {
@Autowired
private TradeService tradeService;
public void testCreateOrder() {
// omit business logic ...
}
}
/**
* @author mghio
* @since 2021-07-17
*/
@Service
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class TradeService {
@Autowired
private OrderService orderService;
public void testCreateTrade() {
// omit business logic ...
}
}
If you Spring
, it can start successfully. The reason is that org.springframework.beans.factory.support.DefaultListableBeanFactory
the preInstantiateSingletons()
method of class 060f2b1388bbd9 is pre-instantiated, multiple types of Bean
filtered out. The method part code is as follows:
But if there are other singleton types of Bean
depend on these multiple types of Bean
, the circular dependency error shown below will be reported.
The third scenario-setter injection of proxy objects
This kind of scenario is often encountered. Sometimes in order to implement asynchronous calls @Async
annotations are added to the methods of the XXXXService
class to make the method asynchronous calls to the outside (provided that the enabling annotations are added to the enabled class @EnableAsync
), examples code show as below:
/**
* @author mghio
* @since 2021-07-17
*/
@EnableAsync
@SpringBootApplication
public class BlogMghioCodeApplication {
public static void main(String[] args) {
SpringApplication.run(BlogMghioCodeApplication.class, args);
}
}
/**
* @author mghio
* @since 2021-07-17
*/
@Service
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class OrderService {
@Autowired
private TradeService tradeService;
@Async
public void testCreateOrder() {
// omit business logic ...
}
}
/**
* @author mghio
* @since 2021-07-17
*/
@Service
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class TradeService {
@Autowired
private OrderService orderService;
public void testCreateTrade() {
// omit business logic ...
}
}
In the scene marked with @Async
annotation, after adding the asynchronous annotation ( @EnableAsync
) enabled, the proxy object will be automatically generated AOP
Running the above code will throw BeanCurrentlyInCreationException
exception. The general flow of operation is shown in the figure below:
The source code is in the method doCreateBean
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory
class. It will judge whether earlySingletonObjects
is equal to the original object. The source code of the method judgment part is as follows:
The object stored in the secondary cache is AOP
, which is not equal to the original object, so a circular dependency error is thrown. If you look closely at the source code, you will find that if the secondary cache is empty, it will return directly (because there are no objects to compare, and it cannot be verified at all), and the circular dependency error will not be reported. By default, Spring
is in accordance with File full path recursive search, sorted by path +
file name, the sort is loaded first, so we only need to adjust the two class names so that
@Async
annotation is sorted at the back.
The fourth scenario-constructor injection
There are very few constructor injection scenarios. So far, I have not encountered any use of constructor injection in the company projects and open source projects I have been in contact with. Although it is not used much, I need to know Spring
does not support the cycle of this scenario. Dependent, the sample code injected by the constructor is as follows:
/**
* @author mghio
* @since 2021-07-17
*/
@Service
public class OrderService {
private TradeService tradeService;
public OrderService(TradeService tradeService) {
this.tradeService = tradeService;
}
public void testCreateOrder() {
// omit business logic ...
}
}
/**
* @author mghio
* @since 2021-07-17
*/
@Service
public class TradeService {
private OrderService orderService;
public TradeService(OrderService orderService) {
this.orderService = orderService;
}
public void testCreateTrade() {
// omit business logic ...
}
}
Constructor injection cannot be added to the third-level cache. The third-level cache in the Spring
framework is useless in this scenario, so an exception can only be thrown. The overall process is as follows (the dotted line indicates that it cannot be executed, and the next step is drawn for intuitiveness. Out):
The fifth scenario-DependsOn cyclic dependency
This kind of DependsOn
cyclic dependency scenarios is rare, and it is not used much in general. Just understand the problems that will cause cyclic dependencies. @DependsOn
annotations are mainly used to specify the order of instantiation. The sample code is as follows:
/**
* @author mghio
* @since 2021-07-17
*/
@Service
@DependsOn("tradeService")
public class OrderService {
@Autowired
private TradeService tradeService;
public void testCreateOrder() {
// omit business logic ...
}
}
/**
* @author mghio
* @since 2021-07-17
*/
@Service
@DependsOn("orderService")
public class TradeService {
@Autowired
private OrderService orderService;
public void testCreateTrade() {
// omit business logic ...
}
}
Through the above, we know that if the class here is not marked with @DependsOn
annotation, it can run normally, because Spring
supports singleton setter
injection, but after adding the @DependsOn
annotation of the sample code, a circular dependency error will be reported, the reason is in the class org.springframework.beans.factory.support.AbstractBeanFactory
The method doGetBean()
checks dependsOn
has a circular dependency. If there is a circular dependency, a circular dependency exception will be thrown. The method judgment part of the code is as follows:
to sum up
This article mainly introduces what cyclic dependency is and how Spring
various cyclic dependency scenarios. Only part of the source code involved is listed in the article, and the position in the source code is marked. Interested friends can go to see the complete source code. Finally, Spring
supports various cyclic dependency scenarios as shown in the figure below (PS Spring
version: 5.1.9.RELEASE):
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。