1, the opening

In this lesson, we’ll talk about how Spring IOC solves the problem of loop dependencies. Including the following:

  • What are circular dependencies
  • Spring IoC deals with the idea of loop dependencies
  • Handle cyclic dependencies for example

2. What are circular dependencies

Circular dependencies in Spring IoC are essentially circular references, where two or more beans hold each other, eventually forming a closed loop. As shown in Figure 1, A depends on B, B depends on C, and C depends on A. In such A scenario, initialization of A requires initialization of B, initialization of B requires initialization of C, and C depends on A, so that A can never complete the initialization. This interdependence of objects in a closed loop is called a cyclic dependency.

Figure 1. Circular dependencies

There are two types of circular dependencies that are unsolvable in the Spring IoC usage scenario:

  • Constructor cyclic dependencies: the constructor calls the constructor new for an object, and the arguments depend on another object. Create the class A depends on the class B, new to create the class B found the class B there is no error will be thrown BeanCurrentlyInCreationException anomalies.
  • Prototype bean loop dependencies: The prototype bean initialization process either through the constructor parameter loop dependencies or through the set method loop dependencies will also throw an exception.

However, the loop dependency scenario for the Singleton bean can be solved with a level 3 cache. The following illustrates the solution.

3. Spring IoC handles loop dependencies

Before wrapping up Spring IoC’s approach to handling singleton bean cycle dependencies, let’s review the bean lifecycle, which consists of three steps:

  • Instantiation: The bean constructor is executed and the dependent object in the bean has not yet been assigned a value
  • Set properties: Assign values to dependent objects in the bean, and if the dependent object has not been initialized, the lifecycle of the object is performed first (recursively).
  • Initialization: Performs bean initialization methods, callback methods, etc.

The idea to solve the problem lies in the three steps. The caching mechanism is introduced between the instantiation and property setting steps. The bean that has already created instances but has no properties is put into the cache, and the cache is the instance object that has no properties. Suppose object A and object B depend on each other. The creation of object A needs to reference object B, and the creation of object B also needs object A. When object A is created, it can be put into the cache. When object B is created, object A can be directly referenced from the cache (at this time, object A has only completed the instantiation, and no operation of setting properties is carried out, so it is not A completed object A, which is called semi-finished object A). When the B object completes the creation of an instance of the semi-finished A object (all three steps are completed) and is referenced by the A object, the A object is also created.

There are three levels of caching, each of which serves a different purpose, as shown in the table below:

  • Level 1 cache: Used to store fully initialized beans, that is, beans that have completed three steps and are ready for immediate use.
  • Level 2 cache: Holds raw bean objects that are instantiated but have no populated properties. This is what we call a “semi-finished object” that is built to handle loop dependencies.
  • Level 3 cache: Used to hold bean factory objects that are used to generate instances of bean objects.
The source code level describe
singletonObjects Level 1 cache Used to hold fully initialized beans and beans taken from this cache can be used directly
earlySingletonObjects The second level cache Store the original bean object (not yet populated with properties) to resolve loop dependencies
singletonFactories Three levels of cache Store bean factory objects to resolve loop dependencies

The entire process for resolving cyclic dependencies is:

Fetch the bean instance from the level-1 cache, or if there is no bean instance in the level-2 cache, or if there is no bean instance in the Level-2 cache, the singletonFactories level-3 cache. Because the level 3 cache holds the factory class that produces bean instances, bean instances can be generated from that factory class.

Here you can call the getObject method exposed by the factory class to return a reference to an earlier exposed object, also known as a semi-finished bean, or earlySingletonObject. The semi-finished bean is put into the level 2 cache and deleted from the level 3 cache. Once the semi-finished product is populated with properties, it is moved to the level 1 cache, that is, to be used as a fully initialized instance bean, and the process of handling loop dependencies is complete. Here’s an example to help you understand this idea.

4. Handle cyclic dependency examples

Based on the above thinking, it is assumed that A and B depend on each other. As shown in Figure 2, the getBean method is used when A creates an instance, and A is instantiated using the createBeanInstatnce method. At this point, A is instantiated without any property filling, and the factory class that created A is added to the level 3 cache via the addSingletonFactory method. The factory class in the level 3 cache is used to generate bean instances.

Further down, when instance A’s properties are populated with populateBean, it is found that A depends on B. CreateBeanInstatnce (createBeanInstatnce, createBeanInstatnce, createBeanInstatnce, createBeanInstatnce, addSingletonFactory) When populateBean method is used to populateB’s properties, it is found that B depends on A, and A is instantiated through getBean method.

In this case, cyclic dependency occurs. The getBean method first obtains the instance of A from the level-1 cache, and then searches for the instance of A from the level-1 cache, but still fails to find it. There is no alternative but to find the instance creation factory of A in the level-1 cache to create the instance of A. In the previous step, A has stored the factory class in the three-level cache through addSingletonFactory method, so it calls the factory class of A to create an instance of A, and puts it into the two-level cache and returns it to B to fill in the attributes of B. When B completes the attribute filling, an instance of B is generated. Return to populateBean (A), where A gets an instance of B (the instance of B that completes the property population).

Therefore, A can also complete the property population to produce an initialized instance of A and put it in the level 1 cache. Since THE instance OF A used by B was not filled with attributes, that is, the semi-finished instance of A, the finished instance of A was obtained from the level 1 cache to complete the initialization of the object of B.

FIG. 2 How to deal with cyclic dependence of A and B.

5, summary

This lesson addresses the problem of circular dependencies encountered by Spring IoC, and finds a solution to the singleton bean’s circular dependencies by analyzing the bean creation process and three-level caching techniques, and then reinforces this idea with a simple example of loop dependency handling.