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

The article explains that Spring's second‑level cache can resolve simple circular dependencies, but a third‑level cache is introduced to handle cases involving AOP proxies, allowing delayed proxy creation without breaking the bean lifecycle.

Java Tech Enthusiast
Java Tech Enthusiast
Java Tech Enthusiast
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’s three caches are three Map fields in DefaultSingletonBeanRegistry:

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

Second‑level cache (earlySingletonObjects) : stores partially created beans – the result of new but before property injection and init methods.

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

Why can the second‑level cache solve circular dependencies?

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

Instantiation : new creates an object with default field values.

Initialization : Spring injects properties and runs init methods.

Assume service A depends on B and vice‑versa. During B ’s initialization, it needs A. Although A is not fully initialized yet, its reference has already been placed in the second‑level cache, so B can obtain the early reference and injection succeeds. The circular dependency is resolved entirely at the second‑level cache.

Why introduce a third‑level cache?

The above works only when beans are plain objects. In Spring’s standard lifecycle, AOP proxies are created late, in the post‑processor AnnotationAwareAspectJAutoProxyCreator, after bean initialization. If a bean participating in a circular dependency needs an AOP proxy, the early reference from the second‑level cache would be the raw object, not the proxy, leading to incorrect injection.

Forcing all beans to be proxied immediately would break Spring’s design principle that proxy creation should happen only after a bean’s normal lifecycle, and would add unnecessary overhead.

How does the third‑level cache resolve this conflict?

The third‑level cache stores a factory callback instead of an actual bean. The callback’s getEarlyBeanReference method creates a proxy if the bean is configured for AOP, otherwise returns the raw instance.

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

The factory is invoked only when a circular dependency forces Spring to retrieve the bean early. At that moment, the callback creates the proxy, places it into the second‑level cache, and removes the factory from the third‑level cache.

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 ObjectFactory from third‑level cache and invoke
                        ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                        if (singletonFactory != null) {
                            singletonObject = singletonFactory.getObject(); // triggers AOP proxy creation
                            // 4. Put the created proxy (or raw object) into second‑level cache and remove factory
                            this.earlySingletonObjects.put(beanName, singletonObject);
                            this.singletonFactories.remove(beanName);
                        }
                    }
                }
            }
        }
    }
    return singletonObject;
}

Step‑by‑step process when a circular dependency involves AOP

A instantiation completed : Spring wraps the raw A object in an ObjectFactory and stores it in the third‑level cache (no proxy yet).

A needs B : Spring starts creating B.

B needs A : B calls getSingleton(A).

Trigger delayed execution : First and second caches miss; Spring finds the factory in the third cache and invokes getObject().

On‑demand proxy creation : The factory detects that A requires AOP, creates A_Proxy early, and puts it into the second‑level cache.

B completes initialization : B receives A_Proxy and finishes, moving to the first‑level cache.

Return to A : After B is ready, A continues its lifecycle; it finds its proxy already in the second‑level cache and uses it.

A completes initialization : A_Proxy is placed into the first‑level cache.

If there is no circular dependency, the factory in the third‑level cache is never invoked; the bean follows the normal lifecycle and the proxy is created only at the post‑processor stage.

Conclusion

The third‑level cache stores a factory callback to enable delayed, on‑demand creation of AOP proxies without breaking the standard bean lifecycle. The second‑level cache handles early exposure of raw bean instances, while the third‑level cache handles early exposure of proxied instances.

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 CacheBean Lifecycle
Java Tech Enthusiast
Written by

Java Tech Enthusiast

Sharing computer programming language knowledge, focusing on Java fundamentals, data structures, related tools, Spring Cloud, IntelliJ IDEA... Book giveaways, red‑packet rewards and other perks await!

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.