Backend Development 10 min read

Understanding Spring's Circular Dependency Resolution via Three-Level Caches

This article explains how Spring handles common circular dependencies between singleton Beans by employing a three‑level cache (singletonObjects, earlySingletonObjects, singletonFactories), detailing the prerequisites, cache structures, workflow, and key source code snippets that illustrate the underlying mechanism.

政采云技术
政采云技术
政采云技术
Understanding Spring's Circular Dependency Resolution via Three-Level Caches

1. Introduction

In everyday development, circular dependencies between Beans are common; Spring handles them transparently. This article explains how Spring achieves this.

2. Overview of Circular Dependency

2.1 What is Circular Dependency

Circular dependency occurs when two or more objects reference each other, forming a loop, as shown in the diagram.

public class PersonA {
  @Autowired
  private PersonB personB;
}
public class PersonB {
  @Autowired
  private PersonA personA;
}

2.2 Preconditions for Spring to Resolve Circular Dependency

1. The mutually dependent Beans must be singletons.

2. Dependency injection cannot rely solely on constructor injection, because constructors would cause deadlock.

3. Three-Level Cache Mechanism

3.1 What is the Three-Level Cache

Spring uses a three‑level cache (singletonObjects, earlySingletonObjects, singletonFactories) to manage Bean creation stages.

SingletonObjects (Level 1)

Stores fully instantiated and initialized Beans.

EarlySingletonObjects (Level 2)

Stores early‑exposed Bean instances whose lifecycle is not yet complete.

SingletonFactories (Level 3)

Stores factories that can produce early Bean references via getObject() . When another Bean needs the early Bean, Spring uses the factory to create the instance and later moves it to Level 1.

/** Level 1 cache */
private final Map
singletonObjects = new ConcurrentHashMap<>(256);
/** Level 2 cache */
private final Map
earlySingletonObjects = new HashMap<>(16);
/** Level 3 cache */
private final Map
> singletonFactories = new HashMap<>(16);

3.2 Three-Level Cache Workflow

3.3 Source Code Analysis of the Three-Level Cache

Bean Creation in AbstractBeanFactory.doGetBean

protected
T doGetBean(String name, @Nullable Class
requiredType,
        @Nullable Object[] args, boolean typeCheckOnly) throws BeansException {
    // obtain canonical bean name
    String beanName = transformedBeanName(name);
    Object bean;
    // retrieve from caches
    Object sharedInstance = getSingleton(beanName);
    // ... omitted for brevity ...
    bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
    // create singleton if necessary
    if (mbd.isSingleton()) {
        sharedInstance = getSingleton(beanName, () -> {
            try {
                return createBean(beanName, mbd, args);
            } catch (BeansException ex) {
                destroySingleton(beanName);
                throw ex;
            }
        });
        bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
    }
    return (T) bean;
}

Key Method getSingleton()

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // check Level 1
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            // check Level 2
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                // check Level 3
                ObjectFactory
singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

Adding and Cleaning Caches

protected void addSingleton(String beanName, Object singletonObject) {
    synchronized (this.singletonObjects) {
        this.singletonObjects.put(beanName, singletonObject);
        this.singletonFactories.remove(beanName);
        this.earlySingletonObjects.remove(beanName);
        this.registeredSingletons.add(beanName);
    }
}
protected void addSingletonFactory(String beanName, ObjectFactory
singletonFactory) {
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

4. Summary

Spring resolves circular dependencies using a three‑level cache: Level 1 (SingletonObjects) holds fully created Beans, Level 2 (EarlySingletonObjects) holds partially created Beans, and Level 3 (SingletonFactories) provides factories to create early references, enabling the container to break circular references.

References

https://www.51cto.com/article/702892.html

Backend DevelopmentSpringBeanCircular DependencyThree-level Cache
政采云技术
Written by

政采云技术

ZCY Technology Team (Zero), based in Hangzhou, is a growth-oriented team passionate about technology and craftsmanship. With around 500 members, we are building comprehensive engineering, project management, and talent development systems. We are committed to innovation and creating a cloud service ecosystem for government and enterprise procurement. We look forward to your joining us.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.