What circular dependency problems can Spring not solve?

Foreword

Everyone knows that Spring solves the problem of circular dependencies, and you can also find that Spring uses a three-level cache to solve circular dependencies on the Internet.
But sometimes circular dependency problems will still cause startup errors.
In other words, in some cases, Spring has no way to solve the circular dependency problem.
Let’s explore, what circular dependency scenarios can’t be solved by Spring?

version convention

Spring 5.3.9 (dependency introduced indirectly via SpringBoot 2.5.3)

Text

Scenario 1: Circular dependency of prototype type
Description: A –> B –> A, and A, B are both scope=prototype

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

@Service
@Scope("prototype")
public class B {
    @Autowired
    private A a;
}

In this scenario, the following error will be reported:

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

Analysis:

After the A instance is created, when the populateBean is created, the loading of B will be triggered.
After the B instance is created, when the populateBean is created, the loading of A will be triggered. Due to the scope=prototype of A, A cannot be obtained from the cache, and a brand new A must be created.
In this way, it will enter an infinite loop. Spring certainly cannot solve the circular dependency in this case. Therefore, the check is performed in advance and an exception is thrown.
prototypeCircleRefer

Resolve:

Add @Lazy on properties that require loop injection

Scenario 2: Circular dependency injected by constructor

Description: A –> B –> A, and are all dependent on the constructor

@Service
public class A {
    private B b;
    
    public A(B b) {
        this.b=b;
    }
}

@Service
public class B {
    private A a;
    
    public B(A a) {
        this.a=a;
    }
}

In this scenario, the following error will be reported:

Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'c1' defined in file [/target/classes/com/kvn/beans/circle/constructor/C1.class]: Unsatisfied dependency expressed through constructor parameter 0;
Nested Exception is org.Springframework.beans.factory.unsatisFiedePenException: ERROR CREATING BEAN WITH NAME 'C2' DEFINED in File [/Target/Classe S/COM/KVN/Beans/Circle/Constructor/C2.Class]: UnsatisFied Dependency Expressed Through Constructor parameter 0;
nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'c1': Requested bean is currently in creation: Is there an unresolvable circular reference?

Analysis:

When the A instance is created (createBeanInstance), because it is a construction injection, it will trigger the loading of B at this time.
When the B instance is created (createBeanInstance), it will trigger the loading of A. At this time, A has not been added to the third-level cache, so a brand new A will be created.
In this way, it will enter an infinite loop. Spring cannot solve the circular dependency in this case. Therefore, the check is performed in advance and an exception is thrown.

Resolve:

Add @Lazy on properties that require loop injection
For example: public A(@Lazy B b){...}

Scenario 3: Circular dependency of ordinary AOP proxy beans – it is possible by default

Description:

A –> B –> A, and A, B are common AOP Proxy type beans

Common AOP proxy type refers: the proxy bean generated by the user-defined @Aspect aspect, which is different from the AOP proxy generated by the class marked with @Async

Spring solves the circular dependency problem of ordinary AOP proxy beans by default, and it is taken out here separately for comparison with @Async-enhanced proxy bean scenarios.

Analysis:

Usually, the creation of AOP proxy is handled by BeanPostProcessor when initializeBean.
A is added to the third-level cache after createBeanInstance. The loading of B is triggered when populateBean is called.
B is added to the third-level cache after createBeanInstance. The loading of A is triggered when populateBean is triggered. At this time, there is A in the third-level cache, so the early reference of the bean can be obtained through the third-level cache ObjectFactory#get().

The logic for getting an early reference to a bean is as follows:

// AbstractAutowireCapableBeanFactory#getEarlyBeanReference()
/**
 * Obtain a reference for early access to the specified bean, typically for the purpose of resolving a circular reference.
 * Get an earlier reference to the specified bean, usually used to resolve circular references.
 */
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() & amp; & amp; hasInstantiationAwareBeanPostProcessors()) {
        for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
            exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
        }
    }
    return exposedObject;
}

Ordinary AOP proxies generate proxy classes through AbstractAutoProxyCreator, and AbstractAutoProxyCreator implements SmartInstantiationAwareBeanPostProcessor, so when passing L3 cache getEarlyBeanReference(), you can obtain proxy beans that are finally exposed to the Spring container in advance Early references (normally, a proxy is generated when initializeBean , and now populateBean generates a proxy, so it is called in advance) .

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
        implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
    ?…
}

So, circular dependencies for plain AOP proxy beans are no problem.

Scenario 4: Circular dependency of beans enhanced by @Async

Description:

A –> B –> A, and A is a class marked with @Async

@Service
public class A {
    @Autowired
    private B b;
    
    @Async
    public void m1(){
    }
}

@Service
public class B {
    @Autowired
    private A a;
}

This scenario will report the following error:

Exception in thread "main" org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'p1Service':
Bean with name 'p1Service' has been injected into other beans [p2Service] 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.

You may have deep doubts. The third scenario just said that there is no problem in the scenario where the AOP proxy bean is circularly dependent, and the class marked with @Async is also an AOP proxy. Why is it not working?
So, we have to look at the difference between the proxy generated by @Async and the ordinary AOP proxy?

Analysis:

The creation of proxy is handled by BeanPostProcessor when initializeBean.
A is added to the third-level cache after createBeanInstance. The loading of B is triggered when populateBean is called.
B is added to the third-level cache after createBeanInstance. The loading of A is triggered when populateBean is triggered. At this time, there is A in the third-level cache, so the early reference of the bean can be obtained through the third-level cache ObjectFactory#get().

Ordinary AOP proxies generate proxy classes through AbstractAutoProxyCreator, which implements SmartInstantiationAwareBeanPostProcessor.
The class marked with @Async is generated through AbstractAdvisingBeanPostProcessor, and AbstractAdvisingBeanPostProcessor does not implement SmartInstantiationAwareBeanPostProcessor.

public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSupport implements BeanPostProcessor {
    ?…
}

Therefore, at this time, when the early reference of the bean is obtained through the third-level cache of A, the reference of the original object of the bean is obtained, and the proxy object will not be generated in advance.
At this time, the A object injected into B is not a proxy object. In the end, the A object held in B is not the same object as the bean A in the Spring container (the proxy generated by the bean AinitializeBean in the Spring container).
This situation is obviously problematic, and it does not match our expectations. Therefore, Spring does a check after initializeBean to check whether the bean in the second-level cache is the same as the bean finally exposed to the Spring container. If it is different, an error will be reported.
proxyCircleRefer

In summary, the proxy bean generated by the @Async marked class has a circular dependency, which Spring cannot solve by default.

The early reference of the bean is stored in the second-level cache, which must be the same as the reference of the bean finally exposed to the container.

If the final exposed AOP proxy bean is not the same object reference as the early reference obtained in the third-level cache, it means that the bean injected by circular dependency is not the same as the bean finally exposed to the Spring container, which is not allowed of.

Spring uses a check mechanism to check whether the bean in the second-level cache is the same as the bean finally exposed to the Spring container. If they are different, an error will be reported.

Resolve:

Add @Lazy to the properties that need loop injection, or A plus @DependsOn(value = “b”) or modify the class name of B so that it is instantiated before A (not easy to modify here, we assume it is Test>Test1 >Test, if you want Test1 to be instantiated before Test, you can change Test1 to Tes. This method is the same as @DependsOn, but it is not elegant) The implementation principle of Spring’s @DependsOn

Summary

Spring solves the problem of circular dependencies for us. In the following cases, the circular dependency problem cannot be solved by default:

Circular dependencies of type prototype
Circular dependencies injected by constructor
Circular dependencies for AOP beans of type @Async
These unsolvable scenarios can be solved by @Lazy.

Thinking:

Maybe everyone has another question: Why can the circular dependency problem in these cases be solved after using @Lazy?

—————
Copyright statement: This article is an original article of CSDN blogger “Lao Wang Xueyuan Code”, which follows the CC 4.0 BY-SA copyright agreement. For reprinting, please attach the original source link and this statement.
Original link: https://blog.csdn.net/wang489687009/article/details/120546430