How Spring Solves Circular Dependencies: Inside the Three‑Level Cache
This article provides a detailed walkthrough of Spring's circular‑dependency resolution, explaining the three‑level cache mechanism, step‑by‑step bean creation flow, and the underlying source‑code logic, complete with diagrams and code examples for deep understanding.
1. Basics
1.1 What Is a Circular Dependency?
A circular dependency occurs when two or more beans depend on each other directly or indirectly, forming a loop. Three typical scenarios exist, and a simple demo (Model1 ↔ Model2) illustrates the classic case.
@Service
public class Model1 {
@Autowired
private Model2 model2;
public void test1() { }
}
@Service
public class Model2 {
@Autowired
private Model1 model1;
public void test2() { }
}This demo runs successfully; the following sections dissect Spring's internal execution.
1.2 Three‑Level Cache
Before diving into the code, understand Spring's three caches:
Level‑1 (singletonObjects): Stores fully initialized singleton beans.
Level‑2 (earlySingletonObjects): Holds early references to beans that have been instantiated but not yet fully populated.
Level‑3 (singletonFactories): Contains factories (often proxy factories) that can create bean instances on demand, crucial for breaking circular references.
1.3 Execution Flow Overview
The process can be visualized in three nested layers:
First layer: Spring attempts to retrieve bean A from Level‑1; not found, it creates a factory for A and stores it in Level‑3, then proceeds to create bean B.
Second layer: While creating B, Spring needs A again, so it looks up Level‑3, obtains A’s factory, creates a proxy (or early reference), and places it in Level‑2.
Third layer: Returning to the first layer, Spring now finds A’s early reference in Level‑2, completes A’s property injection, and finally moves A to Level‑1.
This three‑step “nesting doll” approach resolves the circular dependency.
2. Source‑Code Walkthrough
All examples were tested with Spring 5.2.15.RELEASE.
2.1 Entry Point
The demo starts with the Model1 bean; the code path begins at doGetBean(), which fails to find the bean in Level‑1 and proceeds to creation.
2.2 First Layer
Inside doGetBean(), Spring calls doCreateBean(), which registers a factory for Model1 in Level‑3 via addSingletonFactory(). The factory is stored in singletonFactories.
2.3 Second Layer
When creating Model2, Spring again calls doGetBean() and ultimately doResolveDependency() to locate Model1. It finds Model1’s factory in Level‑3, creates an early reference (proxy if AOP is involved), and stores it in Level‑2.
2.4 Third Layer
At this point, Level‑2 contains the early reference for Model1. Spring uses it to satisfy Model2’s dependency, completing Model2’s initialization.
2.5 Return to Second Layer
After Model2 is fully initialized, Spring invokes getSingleton(), which moves the bean from Level‑2 to Level‑1, clearing Level‑2.
2.6 Return to First Layer
Finally, Model1’s remaining properties are injected, and Model1 is promoted to Level‑1, ending the circular‑dependency resolution.
3. Deep Principle Analysis
3.1 Why a Three‑Level Cache?
Level‑1 guarantees Spring’s singleton semantics. Level‑3 stores factories to break cycles by providing early references before a bean is fully initialized. Level‑2 preserves those early references (often proxies) to avoid creating multiple proxy instances when AOP is applied.
3.2 Can Level‑2 Be Removed?
Consider beans A, B, C where A depends on B and C, and both B and C depend on A. If A is proxied (AOP), each lookup would create a new proxy (A1, A2) without Level‑2, breaking singleton guarantees. Level‑2 caches the early proxy, ensuring the same instance is reused.
@Service
public class A {
@Autowired private B b;
@Autowired private C c;
}
@Service
public class B {
@Autowired private A a;
}
@Service
public class C {
@Autowired private A a;
}Thus, Level‑2 is essential when AOP creates distinct proxy objects; without it, multiple proxies would be generated, violating the singleton contract.
4. Summary
The three caches serve distinct purposes:
Level‑1 stores fully initialized singleton beans.
Level‑2 stores early references (often proxies) to resolve circular dependencies when AOP is present.
Level‑3 stores factories that can produce those early references, enabling the break of circular loops.
Understanding this mechanism equips you to debug Spring’s bean creation, recognize where circular dependencies are resolved, and appreciate the design choices behind the cache hierarchy.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Architect's Guide
Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.
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.
