6
头图

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.

1.png

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.

2.png

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.

3.png

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:

4.png

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:

  1. Instantiate a new object (in the heap), but have not yet assigned values to the object properties
  2. Assign a value to the object
  3. Call some methods of the implementation class of BeanPostProcessor Bean has been created and the attribute assignment is complete. At this time, all BeanPostProcessor interface in the container will be called (eg AOP )
  4. Initialization (if InitializingBean is implemented, the method of this class will be called to complete the initialization of the class)
  5. 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:

5.png

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

6.png


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:

7.png

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.

8.png


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:

9.png

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:

10.png

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):

11.png


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:

12.png

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):
13.png


mghio
446 声望870 粉丝