Why Does Spring Use a Third-Level Cache When the Second-Level Cache Already Solves Circular Dependencies?

The article explains Spring's three‑level cache mechanism, shows how the second‑level cache alone can break simple circular dependencies, and details why the third‑level cache is introduced to lazily create AOP proxies without violating the bean lifecycle.

IT Services Circle
IT Services Circle
IT Services Circle
Why Does Spring Use a Third-Level Cache When the Second-Level Cache Already Solves Circular Dependencies?

What Is Spring’s Three‑Level Cache?

Spring implements three Map fields inside DefaultSingletonBeanRegistry:

First‑level cache (singletonObjects) : stores fully initialized beans ready for use.

Second‑level cache (earlySingletonObjects) : holds partially created beans – objects instantiated with new but not yet injected or initialized.

Third‑level cache (singletonFactories) : stores ObjectFactory callbacks that can create a bean instance on demand.

Why the Second‑Level Cache Can Resolve Simple Circular Dependencies

When only plain beans are involved (no AOP), the bean lifecycle can be split into two steps:

Instantiation : a new object is created with default field values.

Initialization : dependencies are injected and init methods run.

Consider beans A and B that depend on each other. During A’s instantiation, its reference is placed in the second‑level cache. When B is being initialized and requires A, Spring retrieves the early reference from the second‑level cache and injects it, allowing both beans to finish initialization without further issues.

Why a Third‑Level Cache Is Still Needed

The above works only for plain beans. If a bean (e.g., A) needs an AOP proxy, the proxy is created later in the post‑processor phase ( AnnotationAwareAspectJAutoProxyCreator). If B receives the raw instance of A from the second‑level cache, it will hold a reference to the unproxied object, breaking the expected behavior once A’s proxy is finally created.

Forcing every bean to be proxied immediately would violate Spring’s design principle that beans should follow the standard lifecycle and would introduce unnecessary overhead.

How the Third‑Level Cache Solves This Conflict

Spring stores a factory callback in the third‑level cache instead of the bean instance:

// After A is instantiated, expose an ObjectFactory in the third‑level cache
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

The getEarlyBeanReference method creates a proxy if the bean is AOP‑eligible; otherwise it returns the raw instance.

When a circular dependency triggers a lookup for bean A, Spring checks the caches in order:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 1. Check first‑level cache
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        // 2. Check second‑level cache
        singletonObject = this.earlySingletonObjects.get(beanName);
        if (singletonObject == null && allowEarlyReference) {
            synchronized (this.singletonObjects) {
                singletonObject = this.singletonObjects.get(beanName);
                if (singletonObject == null) {
                    singletonObject = this.earlySingletonObjects.get(beanName);
                    if (singletonObject == null) {
                        // 3. Retrieve factory from third‑level cache and invoke it
                        ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                        if (singletonFactory != null) {
                            singletonObject = singletonFactory.getObject(); // creates proxy if needed
                            // 4. Move result to second‑level cache and remove factory
                            this.earlySingletonObjects.put(beanName, singletonObject);
                            this.singletonFactories.remove(beanName);
                        }
                    }
                }
            }
        }
    }
    return singletonObject;
}

This lazy‑loading approach ensures that a proxy is created only when a circular dependency actually requires it, preserving the standard bean lifecycle while still breaking the dependency cycle.

Step‑by‑Step Interaction

A is instantiated; its factory is placed in the third‑level cache (no proxy yet).

A’s property injection needs B, so Spring creates B.

B’s property injection needs A; Spring calls getSingleton(A).

Finding A in neither first nor second cache, Spring invokes the factory from the third cache, which creates A’s proxy and stores it in the second‑level cache.

B receives the proxied A and finishes initialization, moving to the first‑level cache.

A later completes its own post‑processing, discovers its proxy already exists in the second‑level cache, and uses it.

Both beans are now fully initialized with the correct proxy instances.

If no circular dependency occurs, the factory in the third‑level cache remains unused, and beans follow the normal lifecycle, receiving their proxies only after all other initialization steps.

Conclusion

The third‑level cache exists solely to defer proxy creation until it is truly needed, allowing Spring to resolve circular dependencies without breaking the bean lifecycle or incurring unnecessary proxy overhead.

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.

AOPSpringCircular DependencyThree-level CacheBean LifecycleDefaultSingletonBeanRegistry
IT Services Circle
Written by

IT Services Circle

Delivering cutting-edge internet insights and practical learning resources. We're a passionate and principled IT media platform.

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.