Spring Chapter 17 circular dependency

1. Circular dependency resolution in the case of prototype Bean

First of all, Spring will not scan prototype beans in the scanning phase. These prototype beans will be created only when called. List several scenarios for prototype beans to briefly describe:
(1) Suppose there are two objects, class A and class B, and both are defined as prototypes

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

    @Autowired
    private B b;
}

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

    @Autowired
    private A a;
}

When get Bean is used to obtain class A beans from the container, the program will directly report an error.
At this time, because there will be no Bean corresponding to class A in the container, it is necessary to create class A. when instantiating an a original object, because it is prototype, it will not expose a lambda expression in the singletonFactories of the three-level cache. Then continue to inject attributes into attribute B, and class B will not exist in the container, so it is also created, and the original object of B generated by instantiation will not be put into the third level cache. In this way, no matter class A or class B can get the required object in the third level cache to complete attribute injection during attribute injection, so it is bound to be unable to finish the life cycle of the Bean, In a dead circle
Spring cannot solve this situation

Although Spring cannot automatically solve the problem of circular dependency, developers can solve it manually by adding an annotation @ Lazy

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

    @Autowired
    @Lazy
    private B b;
}

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

    @Autowired
    private A a;
}

@The Lazy annotation can solve the problem that both of them are prototypes, whether loaded on the B attribute in class A, on the a attribute in class B, or both, because after class a instantiates and generates the a original object, when injecting the B attribute, it is resolved that the B attribute is defined by the @ Lazy annotation, and a temporary proxy object of proxy class B will be generated to complete the injection of the B attribute, Then class a can continue to complete the whole life cycle of the Bean. Similarly, when creating class B, when assigning a value to attribute a, it will re-enter the class a life cycle, and a new class a object will be created to complete the assignment of attribute a, so that class B can also complete the whole Bean life cycle.
@The use of Lazy can break the dependence of class A on class B

(2) Suppose there are two objects, class A and class B, one of which is singleton and the other is prototype

@Component
public class A {

    @Autowired
    private B b;
}

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

    @Autowired
    private A a;
}

As long as one of class A and class B is singleton, no matter whether the other is singleton or prototype, beans will be created normally
As an example above: if class A is a singleton and class B is a prototype, class a will be placed in the level-3 cache singletonFactories after instantiating and generating the original object of class A, and a lambda expression will be exposed in the level-3 cache. Then, when assigning a value to the attribute of B, class B can only be created because class B is a prototype. When class B instantiates and generates the original object of class B, it will not be placed in the level-3 cache, When assigning a value to attribute a, the lambda expression exposed by class a can be obtained from the three-level cache to generate an object (possibly the original object or a proxy object), then class B can complete attribute injection, then complete the whole Bean life cycle and create a Bean object corresponding to class B, so that class a can get the Bean corresponding to class B and complete attribute injection, Then complete the whole Bean life cycle, create a class a corresponding Bean object and put it into the cache

2. Cyclic dependency caused by construction method

The construction method is used in the instantiation stage of the Bean life cycle. It will go through the process of inferring the construction method to determine which constructor to use to instantiate an original object. Circular dependency will also occur. Next, use an example to briefly describe:

@Component
public class A {

    public A(B b) {
        System.out.println(b);
    }
}

@Component
public class B {

    @Autowired
    private A a;
}

Both class A and class B are singleton, and the constructor in class a needs the object of class B as a parameter to complete the construction. When class A is in the instantiation stage, after inferring the construction method, when using the constructor in the example to instantiate the object, first take the type of class B object to BeanFactory, The assumption here is that you can't find it (the logic of finding it is a little complex. It's not described here. You can read another article Spring Chapter 12 inference construction method and mechanism )Then create class B. after instantiation, class B assigns a value to attribute a, but at this time, class a objects cannot be found from the first level cache, the second level cache and the third level cache, so attribute injection cannot be completed, and the life cycle of the Bean cannot be completed. This is because class A is still in the instantiation stage and has not had time to expose the lambda expression to the L3 cache. Therefore, the construction parameters of class a can not obtain the Bean corresponding to class B and fall into circular dependency

Similarly, Spring cannot automatically solve this circular dependency. It needs to be solved by manually adding an @ Lazy annotation to the a attribute in class B. Because after adding @ Lazy annotation, a temporary proxy object can be generated to help class B complete the assignment of attribute a, and then complete the creation of Bean object corresponding to class B. If the Bean corresponding to class B is created successfully, the constructor of class a can get the parameters of type B, complete the instantiation, and then complete the creation of the Bean object corresponding to class A.

3. Circular dependency resolution in @ Async

Before introducing the usage of @ Async, first introduce the source code abstractautowirecapablebeanfactory The position marked with ★ in docreatebean(),
Why is there an if (exposedObject == bean) check?

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
		.........
		
		// 1. Instantiate bean
		// 2. After instantiation
		Object exposedObject = bean;
		try {
			//3. Attribute injection
			populateBean(beanName, mbd, instanceWrapper);
			//4. Initialization
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		} catch (Throwable ex) {
			if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
				throw (BeanCreationException) ex;
			} else {
				throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
			}
		}

		// 5. The logic of circular dependence
		// If it is a circular dependency, earlySingletonExposure is true
		if (earlySingletonExposure) {
			// The tag does not allow circular dependency to avoid looking in the L3 cache again
			Object earlySingletonReference = getSingleton(beanName, false);
			if (earlySingletonReference != null) {
			     // Generally, this if judgment is true
			     // ★ why do you need such a level of verification? 
				if (exposedObject == bean) {
					exposedObject = earlySingletonReference;
				} else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
				    // In the case of asynchrony, you may go to this else logic
					String[] dependentBeans = getDependentBeans(beanName);
					Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
					for (String dependentBean : dependentBeans) {
						if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
							actualDependentBeans.add(dependentBean);
						}
					}
					if (!actualDependentBeans.isEmpty()) {
						throw new BeanCurrentlyInCreationException(beanName,
								"Bean with name '" + beanName + "' has been injected into other beans [" +
										StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
										"] in its raw version as part of a circular reference, but has eventually been " +
										"wrapped. This means that said other beans do not use the final version of the " +
										"bean. This is often the result of over-eager type matching - consider using " +
										"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
					}
				}
			}
		}

		// 6.Bean destruction logic (judge whether there is special destruction logic and put it into the cache)
		.........
		
		return exposedObject;
	}

Verify the exception information thrown in the above source code by using the @ Async annotation, and explain why this if (exposed object = = bean) judgment is required

@Component
public class A {

    @Autowired
    private B b;

    @Async
    public void a() {
        System.out.println(b);
    }
}

@Component
public class B {

    @Autowired
    private A a;

    public void b() {
        System.out.println(a);
    }
}

@Configuration
@ComponentScan("com.dependence")
@EnableAsync  // Turn on asynchronous call
public class SpringDependenceTest {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringDependenceTest.class);
        A a = context.getBean("a", A.class);
        System.out.println(a);

        B b = context.getBean("b", B.class);
        System.out.println(b);
    }
}

Add @ Async to the method in class A. when running, it will directly report the exception information thrown in the above source code.
Note that it must be used together with the @ EnableAsync annotation. This annotation is asynchronous. Otherwise, using the @ Async annotation alone will not take effect.
So why is this exception?
Because @ Async annotation is used, asynchronous threads will use AsyncAnnotationBeanPostProcessor to generate A class A proxy object, so it may not be the same object as the class A object generated by the original thread, and there may be two forms of the same class. If another class also depends on this class at this time, Then when injecting, you don't know which form of object to choose to assign values.

Can this situation be solved? Yes, two solutions are listed below

## 1. Add @ Lazy annotation to attribute b in class A
@Component
public class A {

    @Autowired
    @Lazy
    private B b;

    @Async
    public void a() {
        System.out.println(b);
    }
}

After class A is instantiated, when assigning the B attribute, since the B attribute is defined by the @ Lazy annotation, a temporary proxy object is directly generated to complete the assignment of the B attribute, and class a will disconnect from class B in the whole life cycle. Therefore, there are no class a objects in the first level cache and the second level cache at this time, Therefore, the execution result of the getSingleton(beanName, false) method in the source code is empty, so it will not go to the following comparison and judgment logic. Even if the @ Async annotation generates a proxy object, it will not be affected.
So to sum up: if @ Lazy annotation is added to the b attribute, the comparison judgment logic will be skipped

## 2. Move the @ Async annotation to the method in class B
@Component
public class B {

    @Autowired
    private A a;

    @Async
    public void b() {
        System.out.println(a);
    }
}

This is a sequential problem. After class A is instantiated, dependency injection is performed on the B attribute, and the creation of class a corresponding Bean is completed first. At this time, there is only one class a object in the L2 cache, and there is no class B object. At this time, class B objects only exist in the L3 cache. Therefore, even if the @ Async annotation is defined on the method in class B, a proxy object will be generated, However, since there are no class B objects in the primary cache and the secondary cache at this time, the execution result of the getSingleton(beanName, false) method in the source code is empty, so the following comparison judgment logic is skipped

Keywords: Java Spring Back-end

Added by sid on Wed, 02 Mar 2022 17:21:31 +0200