How Does Spring Resolve Circular Dependencies? Inside the Three‑Level Cache
This article explains how Spring handles circular dependencies in singleton beans by using a three‑level cache, contrasts it with prototype beans that cannot participate, and demonstrates a minimal implementation that mimics Spring's approach, linking the concept to the classic two‑sum algorithm.
Preface
Spring's solution to circular dependencies has become a popular Java interview question. The author reflects on the value of framework‑source questions and sets the stage for a deep dive into how Spring resolves such cycles.
Main Content
When discussing circular dependencies, Spring focuses on the default singleton bean scenario where beans reference each other via properties. Prototype‑scoped beans do not support circular references; attempting to create them triggers a
BeanCurrentlyInCreationExceptionin
AbstractBeanFactory.
The failure occurs because creating a new instance of bean A requires injecting prototype bean B, which in turn requires creating prototype bean A, leading to a potential stack overflow or out‑of‑memory error. Spring therefore throws
BeanCurrentlyInCreationExceptionearly.
Constructor‑based circular dependencies are not supported at all; the official documentation states that they cannot be resolved.
Spring Solves Circular Dependencies
Spring maintains three Map structures—often referred to as a three‑level cache—in
DefaultSingletonBeanRegistry:
singletonObjects : the primary cache holding fully created singleton beans (the "singleton pool").
singletonFactories : stores factories that can create early references to beans.
earlySingletonObjects : holds early (incomplete) bean instances that are not yet fully initialized.
The latter two maps act as stepping stones: during bean creation, Spring places a factory in
singletonFactories, creates an early reference in
earlySingletonObjects, and finally moves the fully initialized bean into
singletonObjects.
These steps are illustrated in the accompanying diagrams (images omitted for brevity).
The Essence of Circular Dependency
Beyond the Spring implementation, the core problem is simply managing a cache of already‑created objects so that dependent beans can be resolved without re‑instantiation. The author provides a minimal Java example that mimics Spring's behavior:
<code>private static Map<String, Object> cacheMap = new HashMap<>(2);
public static void main(String[] args) {
Class[] classes = {A.class, B.class};
for (Class aClass : classes) {
getBean(aClass);
}
System.out.println(getBean(B.class).getA() == getBean(A.class));
System.out.println(getBean(A.class).getB() == getBean(B.class));
}
private static <T> T getBean(Class<T> beanClass) throws Exception {
String beanName = beanClass.getSimpleName().toLowerCase();
if (cacheMap.containsKey(beanName)) {
return (T) cacheMap.get(beanName);
}
Object object = beanClass.getDeclaredConstructor().newInstance();
cacheMap.put(beanName, object);
for (Field field : object.getClass().getDeclaredFields()) {
field.setAccessible(true);
Class<?> fieldClass = field.getType();
String fieldBeanName = fieldClass.getSimpleName().toLowerCase();
field.set(object, cacheMap.containsKey(fieldBeanName) ?
cacheMap.get(fieldBeanName) : getBean(fieldClass));
}
return (T) object;
}
</code>This code demonstrates that by checking the cache first, creating the bean, injecting its fields recursively, and storing the result, circular dependencies are resolved without special framework support.
What? The Problem Mirrors the Two‑Sum Algorithm
The author draws an analogy to the classic LeetCode "two sum" problem: just as a hashmap is used to find a complementary number in one pass, Spring's cache (the three‑level map) is used to locate a needed bean during creation.
Conclusion
If you find yourself stuck in the "source‑code swamp," understanding the underlying cache mechanism can clarify why Spring's solution appears complex. Recognizing the fundamental pattern helps you apply similar techniques in your own dependency‑injection implementations.
macrozheng
Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.