Why Does Spring Use a Third-Level Cache When the Second-Level Cache Can Resolve Circular Dependencies?
Spring’s bean factory uses a three‑level cache—singletonObjects, earlySingletonObjects, and singletonFactories—to handle circular dependencies, but only the third level is needed when AOP proxies are involved, allowing lazy proxy creation without breaking the standard bean lifecycle.
Spring resolves circular dependencies using a three‑level cache stored in DefaultSingletonBeanRegistry. The first level ( singletonObjects ) holds fully initialized beans, the second level ( earlySingletonObjects ) holds partially created beans (instantiated via new but not yet injected), and the third level ( singletonFactories ) holds ObjectFactory callbacks that can create bean instances on demand.
When only plain beans are involved, the second‑level cache is sufficient: during bean A’s instantiation, a placeholder is placed in the second‑level cache; when bean B needs A, it retrieves the placeholder and injection succeeds, breaking the circular reference.
The third cache becomes necessary when AOP proxy creation is required. Spring creates AOP proxies in the post‑processor phase ( AnnotationAwareAspectJAutoProxyCreator), which runs after normal bean initialization. If a circular dependency involves a bean that must be proxied, the second‑level cache would expose only the raw instance, causing B to receive a non‑proxied reference. Later, when A’s proxy is finally created, B would still hold the original object, breaking the contract.
To avoid this, Spring stores a factory callback in the third‑level cache instead of the raw bean. The callback’s getEarlyBeanReference method creates a proxy if the bean is AOP‑eligible, otherwise returns the original instance. The callback is invoked only when a circular dependency forces early reference retrieval: Spring’s getSingleton(beanName, true) checks the first cache, then the second, and finally, if allowed, obtains the ObjectFactory from the third cache and calls getObject(). The resulting proxy (or original bean) is placed into the second‑level cache for the dependent bean to use.
// Simplified three‑check logic in DefaultSingletonBeanRegistry
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
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) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject(); // triggers AOP proxy creation
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}The overall process can be broken into steps:
A instantiation completed : Spring wraps the raw A instance in an ObjectFactory and stores it in the third‑level cache; no proxy is created yet.
A needs B : Bean B is created.
B needs A : B calls getSingleton(A), triggering the third‑level factory.
Lazy proxy generation : The factory detects that A requires an AOP proxy, creates it early (e.g., A_Proxy), and puts it into the second‑level cache.
B completes initialization : B receives the proxy and finishes.
A continues initialization : When A reaches the post‑processor phase, it finds its proxy already present in the second‑level cache and returns it.
Both beans are fully initialized : The proxies reside in the first‑level cache.
If no circular dependency occurs, the third‑level factory remains unused and beans follow the normal lifecycle, receiving proxies only after all dependencies are satisfied.
In short, the third‑level cache stores factory callbacks to lazily create AOP‑proxied beans only when a circular dependency forces early exposure, preserving the standard bean lifecycle while still resolving the dependency.
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.
Programmer XiaoFu
xiaofucode.com – a programmer learning guide driven by the pursuit of profit
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.
