How does Spring Bean solve the circular dependency?

How does Spring Bean solve the problem of circular dependencies?

Abstract: Original source "Taro Source Code", thank you!
As a loyal reader of "Taro Source Code", I record this article as a reference material for understanding and reference of Spring related content.

Analysis #doCreateBean(...)Method The third process: circular dependency treatment . In fact, not just in the circular dependency #doCreateBean(...)treatment method, but are involved in the whole process of loading the bean. Therefore, this article is not just limited to #doCreateBean(...)the method, but the entire loading process was analyzed from the Bean.

What is circular dependency?

Circular dependency is actually a circular reference. Two or more beans refer to each other to form a closed loop. For example, A depends on B, B depends on C, and C depends on A. As shown in the figure:
image.png
Cyclic dependency is actually an endless loop process. When A is initialized, it is found that B is referenced, then B will be initialized, and then it will be found that B references C, ran to initialize C, and found the reference when initializing C If A is completed, A will be initialized again, and the loop will never exit unless there is a termination condition .

There are two types of cyclic dependency scenarios in Spring :

  • The circular dependency of the constructor.
  • The circular dependency of the field attribute.

For the circular dependency of the constructor, Spring cannot solve it, and can only throw BeanCurrentlyInCreationException to indicate the circular dependency, so the following analysis is based on the circular dependency of the field attribute .
Spring can only solve the circular dependency whose scope is singleton.

Resolve circular dependencies

1. getSingleton

Most of the initial method AbstractBeanFactory start loading the bean #doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)method starts.

In the #doGetBean(...)first method based on beanNameobtaining from a single embodiment bean cache, if not empty directly returns .

// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);

Calling #getSingleton(String beanName, boolean allowEarlyReference)method, from a single embodiment acquires the cache. code show as below:

// DefaultSingletonBeanRegistry.java

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    
    
    // 从单例缓冲中加载 bean
    Object singletonObject = this.singletonObjects.get(beanName);
    // 缓存中的 bean 为空,且当前 bean 正在创建
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
    
    
        // 加锁
        synchronized (this.singletonObjects) {
    
    
            // 从 earlySingletonObjects 获取
            singletonObject = this.earlySingletonObjects.get(beanName);
            // earlySingletonObjects 中没有,且允许提前创建
            if (singletonObject == null && allowEarlyReference) {
    
    
                // 从 singletonFactories 中获取对应的 ObjectFactory
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
    
    
                    // 获得 bean
                    singletonObject = singletonFactory.getObject();
                    // 添加 bean 到 earlySingletonObjects 中
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    // 从 singletonFactories 中移除对应的 ObjectFactory
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

This method is mainly obtained from three caches, namely: singletonObjects,, earlySingletonObjectsand singletonFactories. The three are defined as follows:

// DefaultSingletonBeanRegistry.java
        
/**
 * Cache of singleton objects: bean name to bean instance.
 *
 * 一级缓存,存放的是单例 bean 的映射。
 *
 * 注意,这里的 bean 是已经创建完成的。
 *
 * 对应关系为 bean name --> bean instance
 */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
    
/**
 * Cache of early singleton objects: bean name to bean instance.
 *
 * 二级缓存,存放的是早期半成品(未初始化完)的 bean,对应关系也是 bean name --> bean instance。
 *
 * 它与 {@link #singletonObjects} 区别在于, 它自己存放的 bean 不一定是完整。
 *
 * 这个 Map 也是【循环依赖】的关键所在。
 */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
    
/**
 * Cache of singleton factories: bean name to ObjectFactory.
 *
 * 三级缓存,存放的是 ObjectFactory,可以理解为创建早期半成品(未初始化完)的 bean 的 factory ,最终添加到二级缓存 {@link #earlySingletonObjects} 中
 *
 * 对应关系是 bean name --> ObjectFactory
 *
 * 这个 Map 也是【循环依赖】的关键所在。
 */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16)
  • singletonObjects: Cache of singleton objects
  • earlySingletonObjects: Cache of singleton objects exposed in advance (also proxy objects of beans created by ObjectFactory objects).
  • singletonFactories: Cache of singleton object factories ( ObjectFactory objects exposed in advance ).

These three, we generally known as three-level cache :

  • The first level is singletonObjects
  • The second level is earlySingletonObjects
  • The third level is singletonFactories

We have adopted #getSingleton(String beanName, boolean allowEarlyReference)the method, we see how they fit. Before analyzing this method in detail, let us mention the #isSingletonCurrentlyInCreation(String beanName) method and allowEarlyReference variable:

  • #isSingletonCurrentlyInCreation(String beanName)Method: Determine whether the current singleton bean is being created. The bean is being created, that is to say, the bean is being initialized but the initialization is not completed. One such process is actually complementary to Spring's idea of ​​resolving bean circular dependencies. Because the core of Spring's solution to singleton beans is to expose beans in advance .
  • allowEarlyReferenceVariable: The literal meaning is to allow the reference to be obtained in advance. In fact, really mean is, whether to allow the singletonFactoriesby the cache #getObject()methods to get the object. Why is there such a field? The reason is that singletonFactoriesis the know-how to solve the Spring singleton bean lies.

#getSingleton(String beanName, boolean allowEarlyReference) Method, the whole process is as follows:

  • First, the cache singletonObjectsacquisition.
  • If not, and currently assigned beanNamebeing created, and then from the secondary cache earlySingletonObjectsget in.
  • If you, or did not get to and allowed singletonFactoriesby #getObject()acquiring, from the three-level cache singletonFactoriesacquisition. If acquired, through its #getObject()methods, obtaining the object, and added to the secondary cache earlySingletonObjects, the cache and the three singletonFactoriesdeleted. code show as below:
// DefaultSingletonBeanRegistry.java

singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
  • The third-level cache has been upgraded from here to the second-level cache.
  • Therefore, the significance of the presence of the secondary cache, the cache is ObjectFactory three cache #getObject()execution result of the process, early exposure singleton Bean object.
2. addSingletonFactory

All of the above is to get data from the cache, so when are these data put into the cache? #doCreateBean() #addSingletonFactory()This method is used to add beans to the third-level cache

// AbstractAutowireCapableBeanFactory.java

boolean earlySingletonExposure = (mbd.isSingleton() // 单例模式
        && this.allowCircularReferences // 运行循环依赖
        && isSingletonCurrentlyInCreation(beanName)); // 当前单例 bean 是否正在被创建
if (earlySingletonExposure) {
    
    
    if (logger.isTraceEnabled()) {
    
    
        logger.trace("Eagerly caching bean '" + beanName +
                "' to allow for resolving potential circular references");
    }
    // 提前将创建的 bean 实例加入到 singletonFactories 中
    // <X> 这里是为了后期避免循环依赖
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

// DefaultSingletonBeanRegistry.java

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);
		}
	}
}
  • From this code, we can see that singletonFactoriesthis three-level cache is the trick to solving the circular dependency of Spring Bean. At the same time this code occur #createBeanInstance(...)after method, which means that the bean has been created out of the fact, but it is not perfect (no filling properties and initialization), but for other objects that depend on it enough (can Locate the object in the heap based on the object reference), it can be recognized . So at this time, Spring chose to expose the object in advance to let everyone know about it.

In addition, <X>at the #getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean)method is also very important , this will create early initialization Bean may exist AOP proxy, and so on. code show as below:

// AbstractAutowireCapableBeanFactory.java

/**
 * 对创建的早期半成品(未初始化)的 Bean 处理引用
 *
 * 例如说,AOP 就是在这里动态织入,创建其代理 Bean 返回
 *
 * Obtain a reference for early access to the specified bean,
 * typically for the purpose of resolving a circular reference.
 * @param beanName the name of the bean (for error handling purposes)
 * @param mbd the merged bean definition for the bean
 * @param bean the raw bean instance
 * @return the object to expose as bean reference
 */
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;
				exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
			}
		}
	}
	return exposedObject;
}
  • This is why the need for additional Spring singletonFactoriesreason of level three cache, solve the dynamic agents such as circular dependencies of the Spring Bean case, or injected into the circulation of others Bean is the original, rather than through a dynamic proxy!
3. addSingleton

We found that three-level cache singletonFactoriesand secondary cache earlySingletonObjectsvalues are the source, and that a cache where to set it? DefaultSingletonBeanRegistry class can be found in this #addSingleton(String beanName, Object singletonObject)method, as follows:

// DefaultSingletonBeanRegistry.java

protected void addSingleton(String beanName, Object singletonObject) {
    
    
	synchronized (this.singletonObjects) {
    
    
		this.singletonObjects.put(beanName, singletonObject);
		this.singletonFactories.remove(beanName);
		this.earlySingletonObjects.remove(beanName);
		this.registeredSingletons.add(beanName);
	}
}

to sum up:

Solutions for circular dependencies:

  • Spring in the creation of the bean and so it is not completely finished, but in the creation of the bean creation process of ObjectFactory early exposure (ie, added to the singletonFactoriescache).
  • Thus, once the next bean is created when the need to rely on bean, is used directly ObjectFactory of #getObject()methods to obtain a.

Guess you like

Origin blog.csdn.net/weixin_43743650/article/details/113864332