Spring instantiation source code analysis circular dependency CircularReference (13)

Foreword

First of all, what is circular dependency? Simply put, it means referencing each other. Spring supports circular dependencies by default. As for how to solve the circular dependency problem, this is what this chapter will discuss.

// Allow circular dependencies by default
private boolean allowCircularReferences = true;

//Provide set method
public void setAllowCircularReferences(boolean allowCircularReferences) {<!-- -->
    this.allowCircularReferences = allowCircularReferences;
}

Preparation

Two classes need to be created, and they need to have circular dependencies as shown in the figure below.

Create the two simplest TestA objects and TestB objects. The code is as follows:

@Service
public class TestA {<!-- -->

@Autowired
TestB testB;
}

@Service
public class TestB {<!-- -->
@Autowired
TestA testA;
}

Instantiation process of TestA and TestB

Bean instantiation This chapter describes the general bean instantiation process, so start directly from the preInstantiateSingletons method of DefaultListableBeanFactory and set a conditional breakpoint to analyze step by step.

The TestA we created is a singleton object, not a FactoryBean, and does not require lazyInit, so in the next step we go directly to doGetBean.

doGetBean(testA)

When the doGetBean method is reached, it will be obtained from the first-level cache. It is definitely not available at this time, so null is returned directly. Continue to execute the logic in the subsequent else.

getSingleton(testA,testAObjectFactory)

The else logic will enter the getSingleton method with a functional interface. Here I name the passed functional interface testAObjectFactory. For convenience of description, the actual code is as follows

sharedInstance = getSingleton(beanName, () -> {<!-- -->
try {<!-- -->
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {<!-- -->
destroySingleton(beanName);
throw ex;
}
});

The getSingleton method will have a small operation beforeSingletonCreation(beanName), which is to put the testA currently being created into singletonsCurrentlyInCreation, which means that the object is being created.

After marking the testA object, calling testAObjectFactory is the logic of doCreateBean.

doCreateBean(testA)

When doing the logic of creating a Bean, there is an attribute earlySingletonExposure, which translates to early singleton exposure. Whether the singleton object needs to be exposed early is judged by allowCircularReferences, singletonsCurrentlyInCreation and whether testA is a singleton. So you can also understand why circular references are allowed by default and why it is necessary to first mark the object as being created.

boolean earlySingletonExposure = (mbd.isSingleton() & amp; & amp; this.allowCircularReferences & amp; & amp;
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {<!-- -->
if (logger.isTraceEnabled()) {<!-- -->
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

The early exposure here is what everyone knows as early exposure, and the core is addSingletonFactory. The incoming functional interface will be stored in the second-level cache, as shown in the following figure:

populateBean(testA)

Then use the postProcessProperties method of AutowiredAnnotationBeanPostProcessor to actually inject the property value.

After obtaining the Field, continue to create TestB through beanFactory.getBean(testB) to satisfy the instantiation of TestA.

doGetBean(testB)

testB has not been instantiated yet, so it cannot be obtained from the first-level cache. It returns null directly and enters the else logic.

getSingleton(testB,testBObjectFactory)

The getSingleton method will mark testB as being created. Then execute the testBObjectFactory functional interface, which is createBean(testB)

doCreateBean(testB)

Similarly, testB also meets the requirements for early exposure, so testB will also be placed in the third-level cache.

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {<!-- -->
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {<!-- -->
if (!this.singletonObjects.containsKey(beanName)) {<!-- -->
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}

populateBean(testB)

Then continue to enter attributes. Because testB depends on TestA, beanFactory.getBean(testA) will be executed, so directly enter the doGetBean method.

doGetBean(testA)

After the progress doGetBean method is directly getSingleton(testA)

protected <T> T doGetBean(
String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {<!-- -->

String beanName = transformedBeanName(name);
Object beanInstance;

// Eagerly check singleton cache for manually registered singletons.
//! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! The core of the third-level cache
Object sharedInstance = getSingleton(beanName);

getSingleton(testA)

Because the testA object was previously marked as being created, and the first-level cache currently does not have a testA instance object, because testA has not been instantiated at this time. Therefore, the ObjectFactory object of testA exists in the third-level cache.

Recall what we put into our early exposure

addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {<!-- -->
Object exposedObject = bean;
if (!mbd.isSynthetic() & amp; & amp; hasInstantiationAwareBeanPostProcessors()) {<!-- -->
for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {<!-- -->
// Get the reference of the early bean
          exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
}
}
return exposedObject;
}

Therefore, when singletonFactory calls the getObject method, it will execute the getEarlyBeanReference method to obtain the object, put the obtained object into the second-level cache, and store the third-level cache status. At this time, the third-level cache only has testB. The getEarlyBeanReference method is used to determine whether the object needs to be proxied. If not, the original bean will be returned.

initializeBean(testB)

getSingleton(testB,testBObjectFactory)

After executing the testObjectFactory functional interface, the getSingleton(String beanName, ObjectFactory singletonFactory) method in DefaultSingletonBeanRegistry will continue to execute.

Here testB will be put into the first-level cache and the second- and third-level caches will be removed.

protected void addSingleton(String beanName, Object singletonObject) {<!-- -->
// Thread safe
synchronized (this.singletonObjects) {<!-- -->
//Put it into the first-level cache
this.singletonObjects.put(beanName, singletonObject);
// Remove the third level cache
this.singletonFactories.remove(beanName);
// Remove the second level cache
this.earlySingletonObjects.remove(beanName);
//Put the newly initialized bean into the registered map
this.registeredSingletons.add(beanName);
}
}

initializeBean(testA)

getSingleton(testA,testAObjectFactory)

After executing initializeBean(testA), testA will be put into the first-level cache.

How to solve it?

First, we go through the above step-by-step breakpoint method to understand the overall operation method. Here we use a picture to summarize.

It can be seen from the figure that early exposure is the core of solving circular dependencies. AllowCircularReferences and singletonsCurrentlyInCreation are to assist its work and allow it to operate normally in the case of circular dependencies.

Why level three cache?

Unfortunately, I have also been asked this level 3 cache interview question. The interviewer asked me why it was level 3 instead of level 2. This question is actually asking about your personal understanding of spring’s third-level cache.

The following are personal opinions for reference only:

First-level cache singletonObjects, this is undoubtedly necessary. So if it is a second-level cache, we must make a choice between the second-level cache and the third-level cache.

Assume that there is no secondary cache, that is, the cache of earlySingletonObjects does not exist. When circular dependencies occur, object inconsistency will occur. Examples are as follows:

Suppose B and C are injected into A, A is injected into B, and A is also injected into C.

If there is no second-level cache, A in B and C may not be the same. In this case, there will be inconsistency problems, so earlySingletonObjects is required.

Assume that there is no third-level cache, which means that when exposed in advance, the functional interface return object is directly executed and placed in the second-level cache. Personally, I feel there is no problem.

To sum up, it can be secondary, that is, singletonObjects and earlySingletonObjects must exist.

So since two levels can solve the problem, why does spring use three levels of cache? I personally think that three levels can improve efficiency. Not all beans have cyclic dependencies, which means that not all ObjectFactories exposed in advance need to be executed. This may (guess) be done to improve efficiency.

At the end

When constructors between two or more beans form a circular dependency, Spring cannot determine which bean should be created first because the creation of each bean depends on the other beans. In this case, Spring cannot resolve the circular dependency through constructor injection, causing an exception to be thrown.

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘testB’ defined in file [G:\qhyu-spring\spring-framework\spring-qhyu\build\classes\java\ main\com\qhyu\cloud\circlarRefrence\TestB.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name ‘testA’: Requested bean is currently in creation: Is there an unresolvable circular reference?

@Service
public class TestA {<!-- -->

public TestA(TestB testB) {<!-- -->
this.testB = testB;
}

TestB testB;
}
@Service
public class TestB {<!-- -->
public TestB(TestA testA) {<!-- -->
this.testA = testA;
}

TestA testA;
}

How Spring@Lazy solves the problem of constructor circular dependency