How Spring Breaks Circular Dependencies: A Deep Dive into the 3‑Level Cache

This article explains Spring's circular‑dependency problem, introduces the three‑level cache (singletonObjects, earlySingletonObjects, singletonFactories), walks through the full execution flow with code examples, and clarifies why each cache level and AOP proxy factories are essential for correct bean initialization.

Architect
Architect
Architect
How Spring Breaks Circular Dependencies: A Deep Dive into the 3‑Level Cache

1. Basics

A circular dependency occurs when two or more beans depend on each other directly or indirectly, forming a call loop. Spring resolves this using a three‑level cache system.

1.1 What is a circular dependency?

Three typical scenarios exist; the article focuses on the second case, where two beans reference each other.

1.2 Three‑level cache

Before examining source code, understand the three caches:

Level 1 (singletonObjects) : stores fully initialized singleton beans.

Level 2 (earlySingletonObjects) : stores early references to beans that have been instantiated but not yet fully initialized.

Level 3 (singletonFactories) : stores factories (ObjectFactory) that can create bean proxies, used to break circular references.

1.3 Execution flow (case 2)

The process can be visualised in three steps, resembling a nested "Russian doll" pattern:

Attempt to retrieve bean A from Level 1; not found, move to Level 2, then Level 3.

If found in Level 3, obtain the factory, create a proxy (or early reference), place it into Level 2, and remove it from Level 3.

Continue bean creation, eventually moving the fully initialized bean into Level 1.

2. Source‑code walkthrough

All examples assume Spring 5.2.15.RELEASE; other versions may behave differently.

2.1 Code entry

@Service
public class Model1 {
    @Autowired
    private Model2 model2;
    public void test1() {}
}

@Service
public class Model2 {
    @Autowired
    private Model1 model1;
    public void test2() {}
}

This classic circular dependency compiles and runs, but the internal resolution is handled by the cache mechanism described above.

2.2 First layer (doGetBean → getSingleton → createBean)

Spring looks for Model1 in Level 1; not found, proceeds to Level 2, then Level 3. It creates a factory for Model1 and stores it in the third‑level cache.

2.3 Second layer (resolve Model2)

While creating Model1, Spring needs Model2. It repeats the lookup, creates a factory for Model2, and stores it in Level 3.

2.4 Third layer (proxy retrieval)

When Model2 requests Model1, Spring finds the factory for Model1 in Level 3, creates a proxy (or early reference), places it into Level 2, and removes the factory from Level 3.

2.5 Returning to second layer

With the early reference of Model1 now in Level 2, Model2 can be fully initialized. After Model2 finishes, its bean moves from Level 2 to Level 1.

2.6 Returning to first layer

Finally, the original Model1 bean receives the fully initialized Model2, completes its own property injection, and moves to Level 1.

3. Deep analysis of the three‑level cache

3.1 Why three levels?

Level 1 guarantees Spring's singleton semantics. Level 2 exists to store partially‑initialized beans that have been proxied (e.g., for AOP). Level 3 holds factories that can produce those proxies, allowing the container to break the circular call chain.

3.2 Can Level 2 be removed?

If AOP is involved, removing Level 2 would cause multiple proxy instances for the same bean (e.g., A1 and A2), breaking singleton guarantees. Therefore Level 2 caches the early reference so subsequent lookups reuse the same proxy.

3.3 What if no AOP?

When beans are not subject to AOP, only Level 1 and Level 3 are sufficient because the proxy factory simply returns the original instance.

4. Summary

The three caches serve distinct purposes:

Level 1 – stores fully initialized singleton beans.

Level 2 – stores early references (often AOP‑proxied beans) to avoid duplicate proxy creation.

Level 3 – stores factories that create those early references, enabling Spring to resolve circular dependencies.

Understanding this mechanism is essential for debugging complex bean graphs and for designing Spring applications that avoid hidden circular‑dependency pitfalls.

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.

javaAOPSpringCircular DependencyThree-level Cachebeanfactory
Architect
Written by

Architect

Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.

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.