Analyze the circular dependency in Spring from the perspective of source code

Cyclic dependency

Cross references between objects.
For example, A holds object B, while B also holds object A

There are also multiple objects that reference each other; For example, A holds B, B holds C, and C holds A.

Solution

Spring uses three caches to solve circular dependency (i.e. first, second and third level caches), and makes objects that are not subject to dependency injection available by exposing objects in advance.

Some containers of Spring

Circular dependencies are not allowed

Of course, it does not mean that circular dependencies can be allowed at any time. Here are two simple ways

1. Constructor injection

A. B two objects are passed in as parameters of the constructor, and dependency injection is performed in the constructor; Circular dependencies are not supported.

Code example

A object

@Component
public class A {

    private B b;

	// Label automatic assembly in constructor
    @Autowired
    public A(B b) {
        this.b = b;
    }
}

B object

@Component
public class B {

    private A a;

	// Label automatic assembly in constructor
    @Autowired
    public B(A a) {
        this.a = a;
    }
}

Reported error

After simple screening, the following errors are the core of the problem.

Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'a' defined in file [C:\DemoAndPirace\Spring\target\classes\com\flj\whilerefer\A.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'b' defined in file [C:\DemoAndPirace\Spring\target\classes\com\flj\whilerefer\B.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'a' defined in file [C:\DemoAndPirace\Spring\target\classes\com\flj\whilerefer\A.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'b' defined in file [C:\DemoAndPirace\Spring\target\classes\com\flj\whilerefer\B.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'b' defined in file [C:\DemoAndPirace\Spring\target\classes\com\flj\whilerefer\B.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
	
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
	

Core issues

The Spring error directly indicates whether there is a circular dependency, which makes it impossible to instantiate beans normally

Simple principle

Why can't cyclic dependencies be injected through the constructor?

Because Spring will inject another object before instantiating the object when it creates the object; Note that it is instantiation.

When you need to inject another object, you need to create another object; But another object also holds itself, so it forms a doll.

2. setter injection of multiple objects cannot be cyclic dependent

The second case is that multiple Bean objects cannot have circular dependency

Code example

A object

@Component
@Scope("prototype")
public class A {
    @Autowired
    private B b;
}

B object

@Component
@Scope("prototype")
public class B {

    @Autowired
    private A a;
}

For the main method, you must pay attention to getting the object manually

public class SpringCon {
    public static void main(String[] args) 

        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AnnotationConfig.class);
        // Be sure to get the Bean object
        // Because in the Spring container, multiple objects are obtained when they are used
        // If the object is not used, it will not be created; You cannot test cyclic dependencies
        Object a = context.getBean("a");


    }
}

Abnormal error

Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'a': Unsatisfied dependency expressed through field 'b'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'b': Unsatisfied dependency expressed through field 'a'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'b': Unsatisfied dependency expressed through field 'a'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

Reason why circular dependency cannot be

Because in many cases, we don't know which object to rely on circularly.

Therefore, Spring excludes multiple cases in the condition of circular dependency.

The source code will be discussed later.

===================================================

The following modules need to have a simple understanding of the Spring life cycle.

Probably from the perspective of source code, the classes involved have not been posted

===================================================

Data structure support

• circular dependency is solved by using three cache structures. It does not mean that there must be three. In fact, one or three can be solved. Multiple L2 caches are only used to improve efficiency; And the L3 cache is a factory object

• secondly, it also involves a container that represents the beandefinition being instantiated and a variable that allows Bean instances to be exposed in advance.

Three Map diagrams (Level 1, 2 and 3 cache respectively from top to bottom)

Simple process analysis

First of all, be clear: if a class contains member variables with reference types and is annotated with @ Autowire, the corresponding reference object, beanfactory, will be instantiated during dependency injection GetBean (referenced BeanName);

Circular dependency involves IOC, DI and dependency injection of Spring.

Briefly describe the creation of Bean objects:
Parse XML - > instantiate the object through the parameterless constructor - > collect the annotation of the member variables of the Bean object - > expose the Bean object in advance - > dependency injection - > put it into the first level cache - > destroy it later

Create object legend:

Processing flow

Suppose the following is the flow of object A



At this time, dependency injection will trigger the creation of another object B, which is the same as above.
When B object also performs dependency injection, we find that A needs to be injected, and then we go to the front


Why do I need L2 cache

Here's why there is a L2 cache
We can see that the L3 cache returns a factory, ObjectFactory
His getObject method is complex, so the object is directly extracted and stored in an intermediate cache.

In this way, if a injects many circular dependency objects, other beans need to get the instance of A. they only need to get the instance object from the L2 cache. There is no need to go to the L3 cache to get the factory for generation, avoiding some repeated and cumbersome operations.

Why circular dependencies are not supported

• first, let's parse the constructor
1) The single instance can realize circular dependency because it has instantiated the object, but does not assign a value to the attribute in the object, so this address can always be used

2) By constructor injection, the object needs to be passed in to successfully instantiate the object, otherwise the object cannot be instantiated
3) Summary: setter method injection refers to the instantiated object; Constructor injection is the lack of conditions for instantiating objects, resulting in failure to instantiate, and an error is reported only when a deadlock occurs

• multiple unsupported reasons

1) Because multiple instances cannot expose objects in advance, when I come in for the second time, I will find that my object is already instantiating, so I report an error

2) And multiple objects are inherently lazy to load

Conditions for early exposure of objects

Singleton, which allows circular dependency. The singleton object is being instantiated

Tags: Spring ioc

Posted by Hitman2oo2 on Mon, 18 Apr 2022 04:22:58 +0930