How Spring Solves Circular Dependencies and the Underlying Essence

This article explains how Spring resolves circular dependencies in singleton beans using a three‑level cache, contrasts it with prototype beans, provides a minimal reflective implementation, and draws an analogy to the classic two‑sum algorithm to illustrate the core principle.

Java Captain
Java Captain
Java Captain
How Spring Solves Circular Dependencies and the Underlying Essence

Spring's handling of circular dependencies—especially in singleton beans—has become a popular Java interview topic, and this article walks through the mechanism Spring uses and the fundamental idea behind it.

When a prototype bean participates in a circular reference, Spring detects it early in AbstractBeanFactory and throws a BeanCurrentlyInCreationException, as shown in the snippet:

if (isPrototypeCurrentlyInCreation(beanName)) {
  throw new BeanCurrentlyInCreationException(beanName);
}

For singleton beans, Spring employs a three‑level cache inside DefaultSingletonBeanRegistry:

singletonObjects – the final cache holding fully created singleton instances.

singletonFactories – factories that can create early references to beans.

earlySingletonObjects – temporary storage for early (incomplete) bean references used during wiring.

The two auxiliary caches act as stepping stones, allowing Spring to expose a bean reference before its full initialization is complete, then later replace it with the fully constructed bean.

To illustrate the concept, the author provides a compact Java example that mimics Spring's behavior using a static Map<String, Object> as a cache and reflection to inject fields, handling circular references automatically:

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));
}
@SneakyThrows
private static <T> T getBean(Class<T> beanClass) {
    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 author then abstracts the essence of circular dependency to the classic "two‑sum" problem: locating two numbers that add up to a target using a single pass and a hash map. The solution mirrors Spring's cache‑first approach:

class Solution {
    public int[] twoSum(int[] nums, int target) {
        Map<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < nums.length; i++) {
            int complement = target - nums[i];
            if (map.containsKey(complement)) {
                return new int[]{map.get(complement), i};
            }
            map.put(nums[i], i);
        }
        throw new IllegalArgumentException("No two sum solution");
    }
}

By first checking the cache (map) for a needed value and then storing the current value, the algorithm demonstrates the same principle as Spring's early‑reference handling.

In conclusion, understanding Spring's three‑level cache and its analogy to simple hash‑map algorithms helps demystify the seemingly complex process of resolving circular dependencies.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

BackendDesign Patternsjavaspringdependency-injectioncircular-dependency
Java Captain
Written by

Java Captain

Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.

0 followers
Reader feedback

How this landed with the community

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.