Backend Development 19 min read

Master Spring’s Circular Dependency: How the Three‑Level Cache Resolves It

This article explains what circular dependency means in Spring, the conditions under which it can be resolved, and walks through the three‑level cache mechanism—including simple cases and AOP‑enhanced scenarios—providing code examples, diagrams, and a clear summary for interview preparation.

macrozheng
macrozheng
macrozheng
Master Spring’s Circular Dependency: How the Three‑Level Cache Resolves It

Introduction

Spring’s circular dependency is a classic interview topic because the framework implements sophisticated handling in its source code, and a solid answer can become a decisive advantage in technical interviews.

What is a circular dependency?

A circular dependency occurs when Bean A depends on Bean B while Bean B simultaneously depends on Bean A.

Typical Java code:

<code>@Component
public class A {
    // A injects B
    @Autowired
    private B b;
}

@Component
public class B {
    // B injects A
    @Autowired
    private A a;
}</code>

A self‑reference example:

<code>@Component
public class A {
    // A injects itself
    @Autowired
    private A a;
}</code>

When can circular dependencies be resolved?

Two pre‑conditions must be satisfied:

The involved beans must be singletons.

Not all injections may be constructor‑based; at least one setter/field injection is required.

Constructor‑only injection fails:

<code>@Component
public class A {
    public A(B b) { }
}

@Component
public class B {
    public B(A a) { }
}</code>

Resulting exception:

<code>Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?</code>

Four test scenarios illustrate the rule:

Both beans use setter injection – resolved.

Both beans use constructor injection – not resolved.

A uses setter, B uses constructor – resolved.

B uses setter, A uses constructor – not resolved.

How does Spring solve circular dependencies?

Spring distinguishes two situations: simple circular dependencies (no AOP) and circular dependencies involving AOP proxies.

Simple circular dependency (no AOP)

Bean creation consists of three steps:

Instantiation –

createBeanInstance

creates a raw object.

Property injection –

populateBean

sets dependencies.

Initialization –

initializeBean

runs aware callbacks, init methods, and (if applicable) AOP proxy creation.

Spring maintains three caches:

singletonObjects – fully created singletons.

earlySingletonObjects – instantiated objects awaiting property injection.

singletonFactories – factories that can produce an early reference.

When Bean A is instantiated, a factory for its early reference is placed into

singletonFactories

. During property injection of A, Spring needs Bean B, so it creates B. While creating B, Spring calls

getBean(A)

, which finds the factory in the third‑level cache, invokes it, and obtains the early reference of A to inject into B.

When no AOP is involved,

getEarlyBeanReference

simply returns the raw instance.

Circular dependency with AOP

If a bean is eligible for AOP proxying,

getEarlyBeanReference

returns the proxy instead of the raw instance. Consequently, B receives a proxy of A.

After initialization, Spring calls

getSingleton

again, this time retrieving the proxy from the second‑level cache and placing it into the singleton pool.

The factory is used to delay proxy creation until it is actually needed; otherwise the bean would be proxied prematurely, violating Spring’s design principle of applying AOP at the final stage of the bean lifecycle.

Does the three‑level cache improve performance?

Without AOP, the third‑level cache provides no benefit. With AOP, the total time spent is identical; the only difference is the moment the proxy is created. Therefore, claims that the three‑level cache boosts efficiency are inaccurate.

Summary

Spring resolves circular dependencies by exposing an early reference through a three‑level cache:

singletonObjects – fully initialized singletons.

earlySingletonObjects – early‑exposed objects.

singletonFactories – factories that produce the early reference, optionally applying AOP proxies.

The mechanism works only for singleton beans and when at least one injection is not constructor‑based. The factory approach ensures that AOP proxies are created lazily, preserving the intended bean lifecycle.

AOPSpringCircular DependencyThree-level Cachebean lifecycle
macrozheng
Written by

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.

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.