How Spring Uses a Three‑Level Cache to Resolve Circular Dependencies

This article explains Spring's circular dependency problem, distinguishes when such dependencies are harmless, shows why constructor injection can still fail, and details how Spring's three‑level cache with singleton, early‑singleton, and factory caches resolves setter‑based circular dependencies, including code examples and key source snippets.

IT Services Circle
IT Services Circle
IT Services Circle
How Spring Uses a Three‑Level Cache to Resolve Circular Dependencies

Introduction

Hello, I am Tianluo. The Spring circular dependency issue is a classic problem that we will discuss in depth.

What is a circular dependency?

Is a circular dependency always a problem?

Does Spring's circular dependency always cause issues?

How does Spring solve circular dependencies?

Why is a three‑level cache required? Could a two‑level cache work?

1. What Is a Circular Dependency?

A circular dependency occurs when two or more beans depend on each other, forming a closed loop. For example:

@Service
public class BeanA {
    @Autowired
    private BeanB beanB;
}

@Service
public class BeanB {
    @Autowired
    private BeanA beanA;
}

This is a typical circular dependency.

2. Is a Circular Dependency Always a Problem?

Not necessarily. Consider the following simple classes:

// Class A depends on field b
class A {
    public B b;
}

// Class B depends on field a
class B {
    public A a;
}

These can be instantiated without error:

A a = new A();
B b = new B();

a.b = b;
b.a = a;

3. Does Spring's Circular Dependency Always Cause Issues?

Spring’s default singleton + setter injection resolves most circular dependencies, but constructor injection still fails.

@Service
public class BeanA {
    @Autowired
    private BeanB beanB;
}

@Service
public class BeanB {
    @Autowired
    private BeanA beanA;
}
Not a bug , the example does not error because Spring’s default (singleton + setter injection) already solves the circular dependency. Constructor‑injected circular dependencies, however, still cause problems.

When using constructor injection, Spring cannot expose the bean early, leading to a failure:

Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
|  serviceA defined in ...ServiceA.class
↑     ↓
|  serviceB defined in ...ServiceB.class
└─────┘

4. How Does Setter Injection Resolve Circular Dependencies?

4.1 Three‑Level Cache

Spring’s DefaultSingletonBeanRegistry defines three caches:

// First‑level cache: fully initialized singleton beans
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

// Second‑level cache: early exposed “half‑finished” beans (instantiated but not initialized)
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

// Third‑level cache: bean factories that can create the half‑finished beans
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

singletonObjects : final, fully initialized beans.

earlySingletonObjects : partially created beans, only instantiated.

singletonFactories : factories that generate the partially created beans on demand.

4.2 Circular‑Dependency Handling Process

Assume Bean A depends on Bean B and Bean B depends on Bean A.

1. Creation of Bean A

Step 1: Spring checks the first‑level cache (empty) and marks A as “currently in creation”.

Step 2: Instantiates A (new ServiceA()), producing a half‑finished bean.

Step 3: Adds A’s factory to the third‑level cache.

Step 4: While injecting A’s properties, Spring discovers the dependency on B and pauses A’s creation to create B.

2. Creation of Bean B and Resolution

Step 1: Instantiate B and place its factory in the third‑level cache.

Step 2: When B needs A, Spring looks up A: first‑level cache miss, second‑level cache miss, then retrieves A’s factory from the third‑level cache, creates a half‑finished A, stores it in the second‑level cache, and removes the factory.

Step 3: B receives the half‑finished A, completes its initialization, and moves to the first‑level cache.

Step 4: Resume A’s creation, retrieve the fully initialized B from the first‑level cache, inject it, complete A’s initialization, and move A to the first‑level cache.

The key source code for cache lookup:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // Check first‑level cache
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            // Check second‑level cache
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                // Check third‑level cache
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject(); // create half‑finished bean
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

5. Why Is a Three‑Level Cache Necessary? Can a Two‑Level Cache Suffice?

Reason: AOP proxy . If a bean needs to be proxied, the third‑level factory creates the proxy object rather than the raw instance. Storing the raw instance directly in a second‑level cache would cause later proxying to inject an inconsistent object. The factory ensures the injected reference is the final proxy.
图片
图片
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.

Javaspringdependency-injectioncircular-dependencyThree-level Cache
IT Services Circle
Written by

IT Services Circle

Delivering cutting-edge internet insights and practical learning resources. We're a passionate and principled IT media platform.

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.