forward from:https://juejin.cn/post/6882266649509298189
We all know that Spring solves circular dependencies through the third-level cache, but does it really need to use the third-level buffer to solve circular dependencies? Is it possible to use only two levels of cache? This article is an introduction to how Spring uses the third-level cache to solve circular dependencies, and verifies whether the two-level cache can solve circular dependencies.
circular dependency
Since we want to solve circular dependencies, we must know what circular dependencies are. As shown below:

From the above figure, we can see that:
A depends on B
B depends on C
C depends on A
public class A {
private B b;
}
public class B {
private C c;
}
public class C {
private A a;
}
This dependency forms a closed loop, creating a situation of circular dependencies.
Here are the general steps for unresolved circular dependencies:
1. Instantiate A. At this time, A has not completed the execution of property filling and initialization method (@PostConstruct).
2. The A object finds that it needs to inject the B object, but there is no B object in the container (if the object is created and the property injection is completed and the initialization method is executed, it will be put into the container).
3. Instantiate B. At this time, B has not completed the execution of property filling and initialization method (@PostConstruct).
4. The B object finds that the C object needs to be injected, but there is no C object in the container.
5. Instantiate C. At this time, C has not completed the execution of property filling and initialization method (@PostConstruct).
6. The C object finds that the A object needs to be injected, but there is no A object in the container.
7. Repeat step 1.
L3 cache
The core of Spring's solution to circular dependencies is to expose objects in advance, and the objects exposed in advance are placed in the second-level cache. The following table is a description of the L3 cache:
name | describe |
---|---|
singletonObjects | The first-level cache stores complete beans. |
earlySingletonObjects | The second-level cache stores the beans exposed in advance. The beans are incomplete, and the attribute injection and the init method are not completed. |
singletonFactories | The third-level cache stores bean factories, mainly producing beans, which are stored in the second-level cache. |
All beans managed by Spring will eventually be stored in singletonObjects. The beans stored here have gone through all life cycles (except the life cycle of destruction), are complete, and can be used by users.
earlySingletonObjects stores the beans that have been instantiated, but have not yet injected properties and executed the init method.
singletonFactories store the factories that produce beans.
Beans have already been instantiated, so why do you need a factory that produces beans? This is actually related to AOP. If there is no need to proxy for beans in the project, then the bean factory will directly return the object that was instantiated at the beginning. If you need to use AOP for proxying, then this factory will play an important role. , which is also one of the issues that this paper needs to focus on.
resolve circular dependencies
How does Spring solve circular dependencies through the three-level cache introduced above? Here only the circular dependency formed by A and B is used as an example:
1. Instantiate A. At this time, A has not completed the execution of property filling and initialization method (@PostConstruct), and A is only a semi-finished product.
2. Create a Bean factory for A and put it into singletonFactories.
3. The discovery A needs to inject the B object, but the first-level, second-level, and third-level caches are the discovery object B.
4. Instantiate B. At this time, B has not completed the execution of property filling and initialization method (@PostConstruct), and B is only a semi-finished product.
5. Create a Bean factory for B and put it into singletonFactories.
6. It is found that B needs to be injected into the A object. At this time, the object A is not found in the first and second level, but the object A is found in the third-level cache. The object A is obtained from the third-level cache, and the object A is placed in the second-level cache. In the cache, the object A in the third-level cache is deleted at the same time. (Note that A is still a semi-finished product at this time, and the attribute filling and initialization methods have not been completed)
7. Inject object A into object B.
8. Object B completes attribute filling, executes the initialization method, and puts it into the first-level cache, and deletes object B in the second-level cache at the same time. (Object B is already a finished product at this point)
9. Object A gets object B and injects object B into object A. (Object A gets a complete object B)
10. Object A completes the attribute filling, executes the initialization method, and puts it into the first-level cache, and deletes the object A in the second-level cache at the same time.
We analyze the whole process from the source code:
The way to create a bean is inAbstractAutowireCapableBeanFactory::doCreateBean()
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
BeanWrapper instanceWrapper = null;
if (instanceWrapper == null) {
// ① instantiate the object
instanceWrapper = this.createBeanInstance(beanName, mbd, args);
}
final Object bean = instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null;
Class<?> beanType = instanceWrapper != null ? instanceWrapper.getWrappedClass() : null;
// ② Determine whether the object is allowed to be exposed in advance, and if so, add an ObjectFactory directly to the L3 cache
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
// The details of the method of adding the third-level cache are below
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// ③ Fill properties
this.populateBean(beanName, mbd, instanceWrapper);
// ④ Execute the initialization method and create a proxy
exposedObject = initializeBean(beanName, exposedObject, mbd);
return exposedObject;
}
The method of adding L3 cache is as follows:
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) { // judge that this object does not exist in the first level cache
this.singletonFactories.put(beanName, singletonFactory); // add to L3 cache
this.earlySingletonObjects.remove(beanName); // Make sure the second level cache doesn't have this object
this.registeredSingletons.add(beanName);
}
}
}
@FunctionalInterface
public interface ObjectFactory<T> {
T getObject() throws BeansException;
}
Through this code, we can know that after Spring instantiates the object, it will create a Bean factory for it and add this factory to the third-level cache.
Therefore, what Spring exposes in advance is not the instantiated Bean, but the ObjectFactory that wraps the Bean. Why do you do that?
This actually involves AOP. If the created bean is proxied, the injected bean should be the proxy bean, not the original bean. But Spring does not know whether the bean will have circular dependencies at the beginning. Usually (in the absence of circular dependencies), Spring will create a proxy for it after filling the properties and executing the initialization method. However, if there is a circular dependency, Spring has to create a proxy object for it in advance, otherwise an original object is injected, not a proxy object. So here comes where should the proxy object be created ahead of time?
Spring's approach is to create proxy objects in advance in ObjectFactory. it will executegetObject()
method to get the Bean. In fact, the method it actually performs is as follows:
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
// If a proxy is required, the proxy object will be returned here; otherwise, the original object will be returned
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
Because the proxy is done in advance, to avoid the repeated creation of proxy objects later, it will be in theearlyProxyReferences
The objects that have been proxied are recorded in .
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
// record the proxied object
this.earlyProxyReferences.put(cacheKey, bean);
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
Through the above analysis, we can know that the purpose of Spring's need for three-level cache is to delay the creation of proxy objects without circular dependencies, so that the creation of beans conforms to Spring's design principles.
How to get dependencies
We already know the role of Spring's third-level dependencies, but how does Spring obtain dependencies when injecting properties?
he passed agetSingleton()
method to get the required bean.
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// first level cache
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// L2 cache
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// L3 cache
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// Get the Bean from the Bean Factory
singletonObject = singletonFactory.getObject();
// put into the second level cache
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
When Spring populates properties for a bean, it first looks for the name of the object that needs to be injected, and then executes in turngetSingleton()
The method obtains the object to be injected, and the process of obtaining the object is to first obtain it from the first-level cache. If there is no first-level cache, it will be obtained from the second-level cache. If there is no second-level cache, it will be obtained from the third-level cache. There is no cache in the level cache, then it will be executeddoCreateBean()
method to create this bean.
L2 cache
We now know that the purpose of the third-level cache is to delay the creation of proxy objects, because if there is no dependency cycle, then there is no need to create a proxy for it in advance, and it can be delayed until after initialization is complete.
Since the purpose is only to delay, can we not delay the creation, but create a proxy object for it after the instantiation is completed, so that we don't need a third-level cache. Therefore, we can putaddSingletonFactory()
method to transform.
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) { // judge that this object does not exist in the first level cache
object o = singletonFactory.getObject(); // get the bean directly from the factory
this.earlySingletonObjects.put(beanName, o); // add to the second level cache
this.registeredSingletons.add(beanName);
}
}
}
In this case, the proxy object is created directly after each bean is instantiated and added to the second-level cache. The test result is completely normal, and the initialization time of Spring should not have much impact, because if the bean itself does not need a proxy, it will directly return the original bean, and there is no need to go through the complicated process of creating a proxy bean.
in conclusion
Tests have proved that the second-level cache can also resolve circular dependencies. Why does Spring not choose a second level cache, but add an extra layer of cache?
If Spring chooses the second-level cache to solve the circular dependency, it means that all beans need to create proxies for them immediately after the instantiation is completed, and Spring's design principle is to create proxies for the beans after the initialization is completed. Therefore, Spring chose the third-level cache. However, due to the emergence of circular dependencies, Spring has to create the proxy in advance, because if the proxy object is not created in advance, the original object will be injected, which will cause an error.
Here are some of my own summaries:
Through the debug source code, it is found that the third level cache stores the
singletonFactory
, so if there is only two-level cache, the second-level cache should be calledsingletonFactory.getObject()
Get the bean. At this point, if there is no AOP, there is no problem at all. If there is AOP, then the bean after the proxy must be placed in the second-level cache, because Spring does not know whether there is a circular dependency in advance, so it can only put the proxy. After the bean (the proxy object must be created in advance in the case of circular dependency), the problem arises at this time. If there is no circular dependency, and the proxy object is created in advance, it will not conform to Spring's design principle of delaying the creation of proxy objects. This There is no way to solve the problem of the second-level cache, and it must rely on the third-level cache.
You can also see through the source codegetEarlyBeanReference
This method is called only when there is a circular dependency, and the proxy object created in advance is created here, so the third-level cache perfectly solves this problem.
The above are some of the summaries after reading them. If there are any mistakes, please correct me!