Backend Development 9 min read

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.

Java Captain
Java Captain
Java Captain
How Spring Solves Bean Circular Dependencies Using a Three-Level Cache

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.

AOPBackend DevelopmentSpringCircular DependencyThree-level Cachebean lifecycle
Java Captain
Written by

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.

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.