Backend Development 12 min read

Spring Circular Dependency Resolution with Three‑Level Cache and Early AOP Proxy

This article explains how Spring resolves circular dependencies by using a three‑level cache and early bean exposure, detailing the roles of singletonObjects, earlySingletonObjects, and singletonFactories, and showing how Spring AOP proxies are applied during the process.

Top Architect
Top Architect
Top Architect
Spring Circular Dependency Resolution with Three‑Level Cache and Early AOP Proxy

Spring resolves circular dependencies by using a three‑level cache mechanism during bean creation.

The first‑level cache ( singletonObjects ) holds fully initialized beans; the second‑level cache ( earlySingletonObjects ) stores early‑exposed bean instances that have been instantiated but not yet populated; the third‑level cache ( singletonFactories ) stores ObjectFactory instances that can create early bean references, allowing post‑processing such as AOP proxying.

// AbstractBeanFactory.java
protected
T doGetBean(final String name, @Nullable final Class
requiredType,
            @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
    final String beanName = transformedBeanName(name);
    Object bean;
    // 2. Try to get bean from cache
    Object sharedInstance = getSingleton(beanName);
    ...
}

When a bean is being created, Spring registers a singleton factory that returns an early bean reference via getEarlyBeanReference . This method may invoke SmartInstantiationAwareBeanPostProcessor logic to apply early AOP proxying.

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // Try first‑level cache
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            // Try second‑level cache
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                // Retrieve ObjectFactory from third‑level cache
                ObjectFactory
singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    // Obtain bean via getObject(), which applies getEarlyBeanReference
                    singletonObject = singletonFactory.getObject();
                    // Move bean to second‑level cache
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    // Remove from third‑level cache
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

The early bean reference is wrapped with Spring AOP proxy if applicable, and stored in earlySingletonObjects . After property population and initialization, the bean may be replaced by the proxied instance from the second‑level cache.

AbstractAutoProxyCreator ensures that a bean already proxied early is not proxied again, using earlyProxyReferences to track original instances.

Thus, the combination of three‑level caching and early exposure allows Spring to break circular references while still applying AOP and other bean post‑processors.

javaAOPSpringCircular DependencyBeanPostProcessorThree-level Cache
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

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.