SoFunction
Updated on 2025-03-08

Detailed explanation of Level 3 cache and circular dependencies in Spring

1. Preface

Spring's third-level cache and circular dependency are often heard by us, including interviewers, and interviewers will also ask what the third-level cache is? Why do I need a Level 3 cache? What is circular dependency? How does Spring solve circular dependencies? What kind of circular dependency does Spring not solve?

With the above questions, let’s take a closer look at the getBean() process of Spring BeanFactory; this article requires the reader to have a certain amount of Spring source code understanding;

2. Which three caches refer to

The third-level cache actually corresponds to three maps, which are used as member variables in the DefaultSingletonBeanRegistry class;

public class DefaultSingletonBeanRegistry 
    extends SimpleAliasRegistry 
    implements SingletonBeanRegistry {
    /** Cache of singleton factories: bean name to ObjectFactory. */
    private Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
    /** Cache of early singleton objects: bean name to bean instance. */
    private Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
    /** Cache of singleton objects: bean name to bean instance. */
    private Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
    // ...
}
  • Level 3 cache singletonFactory: You can see that the ObjectFactory object is stored. Level 3 cache can create proxy objects in advance based on whether the object needs to create a proxy; or create a normal object;
  • Secondary cache earlySingletonObjects: As the name suggests, it stores an early object, which stores semi-finished objects or proxy objects, which are used to solve the circular dependency problem in the object creation process; (Why is it said to be a semi-finished object here, because the properties of the objects stored here may not be completely injected);
  • First-level cache singletonObjects: The finished object is stored here, and instantiation and initialization have been completed. The objects used in our project are all obtained in the first-level cache, and proxy objects and ordinary objects are stored in the first-level cache;

3. getBean() process

Let’s take a look at the entire process of Level 3 cache through getBeanFactory’s getBean();

Go directly to getBean() in AbstractBeanFactory;

// --------------------------- AbstractBeanFactory ---------------------------
public Object getBean(String name) throws BeansException {
    return doGetBean(name, null, null, false);
}
// --------------------------- AbstractBeanFactory ---------------------------
protected &lt;T&gt; T doGetBean(
    String name, Class&lt;T&gt; requiredType, Object[] args, boolean typeCheckOnly) {
    // ...
    // Let's look at the main process directly    // Create bean instance.
    if (()) {
        // Call DefaultSingletonBeanRegistry#getSingleton() here        // Parameter 1 is beanName        // Parameter 2 is an ObjectFactory functional object        sharedInstance = getSingleton(beanName, () -&gt; {
            try {
                return createBean(beanName, mbd, args);
            }
            catch (BeansException ex) {
                destroySingleton(beanName);
                throw ex;
            }
        });
        beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
    }
    // ...
}

DefaultSingletonBeanRegistry#getSingleton() is called in AbstractBeanFactory#doGetBean(), and the second parameter of this getSingleton() is an ObjectFactory functional object. The implementation logic of this functional object is return createBean();

Let's first look at DefaultSingletonBeanRegistry#getSingleton();

// ------------------------ DefaultSingletonBeanRegistry -----------------------
public Object getSingleton(String beanName, ObjectFactory&lt;?&gt; singletonFactory) {
    synchronized () {
        // 1. Get it from the first level cache first        // If the bean object is obtained, return it directly        // If the bean object is not obtained, enter the subsequent bean object creation process        Object singletonObject = (beanName);
        if (singletonObject == null) {
            if () {
                throw new BeanCreationNotAllowedException();
            }
            // ...
            try {
                // 2. Call getObject() of the ObjectFactory object to create a bean object                // From the above analysis we know that the final implementation is return createBean()                // We need to look at the createBean() process                singletonObject = ();
                newSingleton = true;
            } catch (IllegalStateException ex) {
                singletonObject = (beanName);
                if (singletonObject == null) {
                    throw ex;
                }
            } finally {
                afterSingletonCreation(beanName);
            }
            if (newSingleton) {
                // 3. When the bean is created successfully                // Put the bean object into the first-level cache singletonObjects                // and remove the corresponding value of beanName from the secondary cache and the third cache                addSingleton(beanName, singletonObject);
            }
        }
        return singletonObject;
    }
}
// ------------------------ DefaultSingletonBeanRegistry -----------------------
protected void addSingleton(String beanName, Object singletonObject) {
    synchronized () {
        // Put the bean object into the first-level cache        // and remove the corresponding value of beanName from the secondary cache and the third cache        (beanName, singletonObject);
        (beanName);
        (beanName);
        (beanName);
    }
}

The core implementation is the createBean() of ObjectFactory. Let's look at the createBean() logic;

// --------------------- AbstractAutowireCapableBeanFactory -------------------
protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) {
    RootBeanDefinition mbdToUse = mbd;
    // ...
    try {
        // Call doCreateBean() to create a bean object and return the bean object        Object beanInstance = doCreateBean(beanName, mbdToUse, args);
        return beanInstance;
    } catch (Throwable ex) {
        throw new BeanCreationException();
    }
}
// --------------------- AbstractAutowireCapableBeanFactory -------------------
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, 
                              Object[] args) throws BeanCreationException {
    BeanWrapper instanceWrapper = null;
    if (()) {
        instanceWrapper = (beanName);
    }
    if (instanceWrapper == null) {
        // 1. Instantiate the object        // Construct an instance bean object according to the appropriate construction method        instanceWrapper = createBeanInstance(beanName, mbd, args);
    }
    Object bean = ();
    Class&lt;?&gt; beanType = ();
    if (beanType != ) {
         = beanType;
    }
    // 2. Should I use Level 3 cache? Generally, I will use Level 3 cache    boolean earlySingletonExposure = (() &amp;&amp; 
                                       &amp;&amp;
                                      isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
        // 3. Put beanName and the corresponding functional object ObjectFactory into the Level 3 cache        // The ObjectFactory has already obtained the bean object that has just been instantiated, but only the constructor has been executed        // The getObject() implementation of the ObjectFactory is to call getEarlyBeanReference()        addSingletonFactory(beanName,
                            () -&gt; getEarlyBeanReference(beanName, mbd, bean));
    }
    // 4. Initialize the Bean object    Object exposedObject = bean;
    try {
        // 4.1 Attribute injection        // If A depends on B, getBean(A) will be adjusted when getBean(B)        // If A and B have circular dependencies, getBean(A) -> getBean(B) -> getBean(A) will occur        populateBean(beanName, mbd, instanceWrapper);
        // 4.2 Initialize the bean object        // Here we will execute some post-processing methods of BeanPostProcessor        // The Spring AOP we are familiar with is the proxy class object generated here        // For example, the postprocessor used by @Transactional is AbstractAutoProxyCreator        // For example, the postprocessor used by @Async is AbstractAdvisingBeanPostProcessor        // Although both generate AOP objects, the processing logic of these two is different when dealing with circular dependencies. I will explain in detail later        exposedObject = initializeBean(beanName, exposedObject, mbd);
    }
    catch (Throwable ex) {
        throw ex;
    }
    if (earlySingletonExposure) {
        // 5. Decide whether getBean(A) returns a bean object or throws an exception        // Parameter 2 is false        // Get bean objects from the first-level cache or the second-level cache        // When you get here, you usually try to get bean objects from the secondary cache        // This is more complicated, let's talk about it later        Object earlySingletonReference = getSingleton(beanName, false);
        if (earlySingletonReference != null) {
            if (exposedObject == bean) {
                exposedObject = earlySingletonReference;
            }
            else if (! &amp;&amp; 
                     hasDependentBean(beanName)) {
                String[] dependentBeans = getDependentBeans(beanName);
                Set&lt;String&gt; actualDependentBeans = new LinkedHashSet&lt;&gt;();
                for (String dependentBean : dependentBeans) {
                    if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                        (dependentBean);
                    }
                }
                if (!()) {
                    throw new BeanCurrentlyInCreationException();
                }
            }
        }
    }
    // 6. Register to delete bean logic    try {
        registerDisposableBeanIfNecessary(beanName, bean, mbd);
    }
    catch (BeanDefinitionValidationException ex) {
        throw new BeanCreationException();
    }
    // 7. Return the bean object    return exposedObject;
}

This is the article about Spring Level 3 cache and circular dependence. For more related content about Spring Level 3 cache and circular dependence, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!