SoFunction
Updated on 2025-04-14

In-depth analysis of how Spring solves circular dependencies

1. What is circular dependency

Circular Dependency refers to the situation in which two or more beans depend on each other to form a closed loop. For example:

@Service
public class AService {
    @Autowired
    private BService bService;
}

@Service
public class BService {
    @Autowired
    private AService aService;
}

In the above code, AService depends on BService, and BService depends on AService, forming a circular dependency.

2. Three situations of Spring cyclic dependency

Constructor cyclic dependency: Spring cannot solve the cyclic dependency formed by constructor injection, and will directly throw BeanCurrentlyInCreationException

Setter circular dependency (singleton pattern): Spring can solve the problem of circular dependency formed by the setter method injection.
Prototype circular dependency: The circular dependency formed by scope is a prototype bean, Spring cannot solve it

3. Spring's core idea of ​​solving circular dependencies

Spring solves the problem of Setter circular dependency in singleton mode through a three-level caching mechanism. The core idea is:

Exposure of bean instances that are not fully initialized in advance: During the bean creation process, after instantiation and before initialization, the bean reference is exposed in advance.

Level cache: Use Level 3 cache to store beans in different states to ensure that the correct reference can be obtained during dependency injection

Detailed explanation of Level 4 and Level 3 cache

1. Level 3 cache structure

/** Level 1 cache: store fully initialized beans */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/** Level 2 cache: store the original Bean object (properties not yet filled) */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

/** Level 3 cache: store bean factory objects, used to generate original bean objects */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

2. Bean creation and level three cache interaction process

1. Create a bean instance: Create a bean instance by calling the constructor through reflection

2. Put in the third-level cache: wrap the bean into an ObjectFactory and put in the third-level cache singletonFactories

3. Attribute filling: fill the properties of the bean. If you find that you depend on other beans

  • Get from first-level cache singletonObjects
  • If it does not exist, try to get it from the secondary cache earlySingletonObjects
  • If it does not exist yet, get the ObjectFactory from the third-level cache singletonFactories and generate the bean, and then put it in the second-level cache

4. Initialization: Execute the initialization method (@PostConstruct, etc.)

5. Put in Level 1 cache: Put the fully initialized bean into Level 1 cache, and delete the records in Level 2 and Level 3 caches.

3. Circular dependency solution example

Take the circular dependencies of AService and BService as an example:

1. Start creating AService

  • Instantiate AService
  • Put the AService ObjectFactory into the Level 3 cache

2. When populating the AService property, I found that BService is needed

  • Start creating a BService
  • Instantiate BService
  • Put BService's ObjectFactory into Level 3 cache

3. When populating the BService property, I found that AService is needed

  • Getting AService from Level 1 cache → does not exist
  • Get AService from Level 2 cache → does not exist
  • Get the ObjectFactory of AService from the third-level cache → exists, generate early references of AService and put them into the second-level cache
  • Inject early references to Service

Continue to complete attribute filling and initialization

Created and put into Level 1 cache

Get a fully initialized BService reference

Continue to complete attribute filling and initialization

Created and put into Level 1 cache

5. Source code analysis

The key source code is in the DefaultSingletonBeanRegistry class:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 1. Get it from the first level cache first    Object singletonObject = (beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized () {
            // 2. Get from Level 2 cache            singletonObject = (beanName);
            if (singletonObject == null && allowEarlyReference) {
                // 3. Get ObjectFactory from Level 3 cache                ObjectFactory<?> singletonFactory = (beanName);
                if (singletonFactory != null) {
                    singletonObject = ();
                    // Raise Level 3 cache to Level 2 cache                    (beanName, singletonObject);
                    (beanName);
                }
            }
        }
    }
    return singletonObject;
}

6. Why can't the constructor cyclic dependency solve

Timing problem: Constructor injection occurs in the instantiation stage. At this time, the bean has not been created yet and the reference cannot be exposed in advance.

The cache mechanism is not applicable: the third-level cache mechanism relies on exposing references after instantiation and before initialization, and the instance has not been fully created when the constructor is injected.

7. Why can't the circular dependency of prototype patterns be solved

Different life cycles: Prototype mode creates new instances every time it gets, Spring does not cache prototype beans

Unable to expose references in advance: Without cache mechanism support, it is impossible to share unfinished references during creation.

8. Spring solves the limitations of circular dependencies

Only solve Setter injection loop dependencies for singleton mode

The large amount of use of circular dependencies will cause confusion in the code structure and increase maintenance difficulty

Problems may occur in some AOP scenarios (special treatment is required)

9. Best Practices

Avoid circular dependencies: Refactoring code through design patterns (such as mediator pattern, observer pattern)

Use Setter Injection Alternative Constructor Injection: If you must use circular dependencies

Using @Lazy annotation: lazy loading of dependencies beans

@Service
public class AService {
    @Autowired
    @Lazy
    private BService bService;
}

Using ApplicationContextAware interface: Get dependency beans manually

10. Summary

Spring cleverly solves the circular dependency problem of Setter injection in singleton mode through a level 3 caching mechanism, but it is essentially a compromise. A good system design should try to avoid circular dependencies and maintain clear dependencies. Understanding the mechanism of Spring's solution to circular dependencies will help us better use the Spring framework and be able to quickly locate and resolve related problems when encountering related problems.

This is the end of this article about in-depth analysis of how Spring solves circular dependencies. For more relevant content on Spring to solve circular dependencies, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!