What is circular dependency

What is circular dependence? It can be divided into two parts: loop and dependency. The loop refers to the loop in the computer field. The execution process forms a closed loop. Dependency is the precondition for the completion of this action, and we generally say the same meaning of dependency. If there is a direct or indirect dependency between one or more Bean instances, circular dependencies can be divided into direct circular dependencies and indirect circular dependencies. The simple dependency scenario of direct circular dependencies is: Bean A depends on Bean B, which in turn depends on Bean A (Bean A-> Bean B-> Bean A). A dependency scenario for an indirect loop dependency: Bean A depends on Bean B, Bean B depends on Bean C, Bean C depends on Bean A, there is an extra layer in between, but the loop is still formed (Bean A -> Bean B -> Bean C -> Bean A).

Type of cyclic dependency

The first one is self-dependence, self-dependence which is a circular dependence, which normally doesn’t happen because it’s easy for us to detect.

The second is A direct dependency that occurs between two objects. For example, Bean A depends on Bean B, which in turn depends on Bean A. If you are careful, you can see that.

The third type of dependency is indirect dependency. This type of dependency occurs when three or more objects depend on each other. Indirect dependency is the simplest scenario: Bean A depends on Bean B, Bean B depends on Bean C, and Bean C depends on Bean A. As you can imagine, it is difficult to find such circular dependencies when there are many objects in the middle of dependencies.

Spring supports several cyclic dependency scenarios

Before describing how Spring handles several cyclic dependency scenarios, let’s take a look at the various cyclic dependency scenarios in Spring. Most of the common scenarios are summarized in the following figure:

As the saying goes, there is no secret under the source code, let’s explore whether these scenarios are supported by Spring through the source code, and the reasons for support or not support, without saying much, let’s enter the topic.

The first scenario — setter injection of singleton beans

This is one of the most common ways to use it. Assume that there are two services named OrderService and TradeService. 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 case, the program can run normally. From the code, it does have a cyclic dependency. In other words, Spring supports this cyclic dependency scenario.

Assuming that the container does not do any processing and follows the normal creation logic, the flow is like this: the container creates OrderService and finds that it depends on TradeService, then creates OrderService and finds that it depends on TradeService… , an infinite loop occurs, and finally a stack overflow error occurs, and the program stops. To support this common circular dependency scenario, Spring breaks down object creation into the following steps:

  1. Instantiate a new object (in the heap), but at this point no object properties have been assigned
  2. Assign values to objects
  3. callBeanPostProcessorAt this stage,BeanThe property has been created and assigned. All implementations in the containerBeanPostProcessorA class of the interface will be called.AOP)
  4. Initialize (if implemented)InitializingBean, the class method is called to initialize the class.
  5. Returns the created instance

Therefore, Spring l3 cache is introduced to deal with this problem (l3 cache definition in the org. Springframework. Beans. Factory. Support. DefaultSingletonBeanRegistry), The first-level SingleOnObjects cache is used to hold fully initialized beans that can be used directly from the cache. The second-level EarlySingleOnObjects cache is used to hold the cache of pre-exposed singletons. Stores the original Bean object (properties not yet assigned) for resolving the circular dependencies, and the third level cache SingletonFactories for storing the cache of the singleton object factory for resolving the circular dependencies. The above example uses a three-level cache process as follows:

If you read the source code for the level 3 cache, you might also wonder why the level 3 cache is defined as Map

>, can’t you cache objects directly? You can’t save the object instance directly, because then you can’t enhance it. Details visible class org. Springframework. Beans. Factory. Support. AbstractAutowireCapableBeanFactory# doCreateBean methods part of the source code is as follows:
,>


The second scenario — multi-instance Bean setter injection

The only difference is that both services are now declared as multiple instances. The example 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 run the above code in Spring, it will be successful. Reason is that the class org. Springframework. Beans. Factory. Support. The DefaultListableBeanFactory preInstantiateSingletons () method of the pre instantiation processing, Filter out the beans with multiple example types. The method code is as follows:

However, if there are other beans of the singleton type that depend on the beans of the multiinstance type, the circular dependency error as shown below will be reported.


The third scenario — Setter injection of proxy objects

This scenario is also common. In order to implement an asynchronous call, you will add an @Async annotation to the method of the xxxxService class to make the method asynchronous to the outside (if you need to add an enabling annotation to the enabled class @EnableAsync). The example code is as follows:

/** * @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 a scenario marked with the @Async annotation, the proxy object is automatically generated via AOP after you add the enabled @EnableAsync annotation. The above code to run throws BeanCurrentlyInCreationException anomalies. The general process of operation is shown in the figure below:

The source code in the org. Springframework. Beans. Factory. Support. DoCreateBean method AbstractAutowireCapableBeanFactory class, If the object in the second level cache EarlySingleOnObjects is equal to the original object, the source code for the method is as follows:

The second level cache holds a proxy object generated by AOP, which is not equal to the original object, thus throwing a circular dependency error. If you look at the source code, you will find that if the second level cache is empty, it will return directly (because there are no objects to compare, it is impossible to verify), and there will be no cyclic dependency error. By default, Spring searches by the full path of the file recursively, sorting by path + filename, and loading by sorting first. So we just adjust the two class names so that the classes whose methods are annotated with the @Async annotation come after each other.


The fourth scenario — constructor injection

Constructor injection is a rare scenario. I haven’t seen any corporate projects or open source projects that I’ve worked with so far that use constructor injection. Although it’s not used very often, you need to know why Spring doesn’t support cyclic dependency for this scenario.

/** * @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 level 3 cache. The level 3 cache in the Spring framework has no use in this scenario, so it can only throw an exception. The overall flow is as follows (the dotted line indicates that it cannot be executed, but the next step is also drawn for intuition) :


Scenario ⑤ – DependsOn loop

The @DependsOn annotation is used to specify the order of instantiation. The example 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 ... }}

We know that if the class is not @DependsOn annotated, it will work because Spring supports singleton setter injection, but adding @DependsOn annotated with the sample code will report a cyclic dependency error. Reason is that the class org. Springframework. Beans. Factory. Support. DoGetBean AbstractBeanFactory method () check whether there is the instance of dependsOn circular dependencies, If there is a cyclic dependency, then a cyclic dependency exception is thrown.

conclusion

This article mainly introduces what is circular dependency andSpringThe processing of various cyclic dependency scenarios, the article only lists part of the source code involved, are marked in the source code of the location, interested friends can go to see the complete source code, finallySpringSupport for various cyclic dependency scenarios is shown in the following figure (P.S.SpringVersion: 5.1.9.Release) :