How Spring Solves Bean Circular Dependencies Using a Three-Level Cache
This article explains how Spring's bean lifecycle and its three-level caching mechanism—singletonObjects, earlySingletonObjects, and singletonFactories—work together to resolve circular dependencies, especially when AOP proxies are involved, highlighting why a three‑cache approach is necessary.
Introduction
In daily Spring development, circular dependencies between beans occur frequently. Spring hides the resolution process from developers; this article analyses how Spring solves bean circular dependencies and why it uses a three‑level cache instead of a two‑level one.
Bean Lifecycle
Understanding the bean lifecycle is essential. Spring creates a bean instance, then populates its properties, and finally initializes it. The process involves interfaces such as BeanNameAware, BeanClassLoaderAware, BeanFactoryAware, BeanPostProcessor, InitializingBean, and custom init‑methods.
During doCreateBean , the bean is instantiated via reflection. populateBean injects dependencies; if a required bean is not yet created, Spring proceeds to create it. initializeBean invokes aware methods, BeanPostProcessor callbacks before and after initialization, and any init‑method.
Three‑Level Cache for Circular Dependency
When populating properties, if a dependency is missing, Spring exposes a partially created bean (early reference) through an ObjectFactory stored in the third‑level cache ( singletonFactories ). The three caches are:
singletonObjects – fully initialized singletons.
earlySingletonObjects – early references (second‑level cache).
singletonFactories – factories for early references (third‑level cache).
In a circular scenario (AService ↔ BService), A is instantiated and its factory placed in the third‑level cache. While injecting B, Spring creates B and also places its factory in the third‑level cache. When B needs A, the early reference is obtained from the third‑level cache, moved to the second‑level cache, and later completed.
AOP Proxy Considerations
If a bean is proxied by AOP (e.g., CGLIB), the ObjectFactory returns a proxy object. Re‑creating the proxy each time would break the singleton guarantee, so the second‑level cache stores the proxy to ensure only one instance is used. Therefore, a three‑level cache is required when AOP is involved; a two‑level cache would fail to preserve the singleton proxy.
Conclusion
The article reviewed the Spring bean loading process, explained why Spring employs a three‑level cache to resolve circular dependencies, and demonstrated that the additional cache is essential for handling AOP‑proxied beans while maintaining singleton semantics.
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.
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.