How Spring Solves Circular Dependencies and the Underlying Essence
This article explains Spring's three‑level cache mechanism for breaking circular dependencies in singleton beans, demonstrates a simplified implementation with Java code, draws an analogy to the classic Two‑Sum algorithm, and highlights the core idea behind dependency injection.
Spring's handling of circular dependencies is a common Java interview topic, especially for singleton beans where two or more beans reference each other.
The framework uses three internal maps— singletonObjects , singletonFactories , and earlySingletonObjects —often referred to as a three‑level cache. The first stores fully created singletons, while the latter two act as temporary placeholders during bean creation.
When a bean is being created, Spring first checks if it already exists in the cache; if not, it creates the instance, puts it into the cache, and then injects its fields. If a field's dependency is already in the cache, it is injected directly; otherwise, Spring recursively creates the required bean.
Below is a simplified Java example that mimics this behavior using a static Map<String, Object> as the cache:
private static Map
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 getBean(Class
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;
}The core idea is that the cache holds early references (similar to the second and third maps) so that circular references can be resolved without infinite recursion.
The article also draws a parallel to the classic LeetCode "Two Sum" problem, showing that the same hashmap‑based lookup strategy used to find a pair of numbers adds up to a target is analogous to Spring's bean lookup during dependency injection.
In summary, understanding the three‑level cache and the hashmap lookup pattern helps demystify Spring's circular‑dependency resolution and provides a practical way to implement a lightweight version yourself.
Java Architect Essentials
Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.
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.