Spring-bean circular dependencies and solutions

Spring-bean circular dependencies and solutions

 

This article mainly analyzes the circular dependency of Spring beans and Spring's solution. Through this solution, we can apply it in our actual development project.

  1. What is a circular dependency?
  2. How to detect circular dependencies
  3. How does Spring solve circular dependencies
  4. Spring for scenarios where circular dependencies cannot be resolved
  5. What can we learn from the way Spring resolves circular dependencies?

1. What is a circular dependency?

A circular dependency is actually a circular reference, that is, two or more beans hold each other and eventually form a closed loop. For example, A depends on B, B depends on C, and C depends on A. As shown below:

write picture description here

Note that this is not a cyclic call of a function, but an interdependence of objects. A loop call is actually an infinite loop unless there is a termination condition.

The circular dependency scenarios in Spring include: 
(1) The circular dependency of the constructor 
(2) The circular dependency of the field property.

2. How to detect if there is a circular dependency

It is relatively easy to detect circular dependencies. The bean can be marked when it is created. If the recursive call comes back and finds that it is being created, it means a circular dependency.

3. How does Spring resolve circular dependencies

The theoretical basis of Spring's circular dependency is actually based on Java's reference passing. When we get a reference to an object, the field or property of the object can be set later (but the constructor must be before getting the reference).

The initialization of Spring's singleton object is mainly divided into three steps: 
bean initialization 
(1) createBeanInstance: instantiation, which is actually calling the object's constructor to instantiate the object

(2) populateBean: Populate properties, this step is mainly to populate the dependency properties of multiple beans

(3) initializeBean: call the init method in spring xml.

From the singleton bean initialization steps described above, we can know that circular dependencies mainly occur in the first and second parts. That is, constructor circular dependencies and field circular dependencies.

那么我们要解决循环引用也应该从初始化过程着手,对于单例来说,在Spring容器整个生命周期内,有且只有一个对象,所以很容易想到这个对象应该存在Cache中,Spring为了解决单例的循环依赖问题,使用了三级缓存

首先我们看源码,三级缓存主要指:

/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);

/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);

/** Cache of early singleton objects: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

这三级缓存分别指: 
singletonFactories : 单例对象工厂的cache 
earlySingletonObjects :提前暴光的单例对象的Cache 
singletonObjects:单例对象的cache

我们在创建bean的时候,首先想到的是从cache中获取这个单例的bean,这个缓存就是singletonObjects。主要调用方法就就是:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

上面的代码需要解释两个参数:

  • isSingletonCurrentlyInCreation()判断当前单例bean是否正在创建中,也就是没有初始化完成(比如A的构造器依赖了B对象所以得先去创建B对象, 或则在A的populateBean过程中依赖了B对象,得先去创建B对象,这时的A就是处于创建中的状态。)
  • allowEarlyReference 是否允许从singletonFactories中通过getObject拿到对象

分析getSingleton()的整个过程,Spring首先从一级缓存singletonObjects中获取。如果获取不到,并且对象正在创建中,就再从二级缓存earlySingletonObjects中获取。如果还是获取不到且允许singletonFactories通过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取,如果获取到了则:

this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
  • 1
  • 2

从singletonFactories中移除,并放入earlySingletonObjects中。其实也就是从三级缓存移动到了二级缓存。

从上面三级缓存的分析,我们可以知道,Spring解决循环依赖的诀窍就在于singletonFactories这个三级cache。这个cache的类型是ObjectFactory,定义如下:

public interface ObjectFactory<T> {
    T getObject() throws BeansException;
}
  • 1
  • 2
  • 3

这个接口在下面被引用

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);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

这里就是解决循环依赖的关键,这段代码发生在createBeanInstance之后,也就是说单例对象此时已经被创建出来(调用了构造器)。这个对象已经被生产出来了,虽然还不完美(还没有进行初始化的第二步和第三步),但是已经能被人认出来了(根据对象引用能定位到堆中的对象),所以Spring此时将这个对象提前曝光出来让大家认识,让大家使用。

What's the benefit of doing this? Let's analyze the circular dependency situation of "a field or setter of A depends on the instance object of B, and a field or setter of B depends on the instance object of A". A first completed the first step of initialization, and exposed itself to singletonFactories in advance. At this time, the second step of initialization, and found that he was dependent on object B. At this time, he tried to get(B) and found that B had not been created. , so in the create process, B found himself dependent on object A when initializing the first step, so he tried get(A), tried the first-level cache singletonObjects (certainly not, because A has not been initialized completely), and tried the second-level cache earlySingletonObjects (No), try the three-level cache singletonFactories, since A exposes itself in advance through ObjectFactory, B can get the A object through ObjectFactory.getObject (although A has not been fully initialized, but it is better than nothing), B takes After reaching the A object, the initialization stages 1, 2, and 3 have been successfully completed. After complete initialization, it will put itself into the first-level cache singletonObjects. At this time, returning to A, A can get the object of B and successfully complete its initialization stages 2 and 3. Finally, A also completes the initialization and enters the first-level cache singletonObjects, and even luckier, because B got A's object reference, so the A object that B now holds is initialized.

When you know this principle, you must know why Spring can't solve the problem of "A's construction method depends on B's instance object, and B's construction method depends on A's instance object"! Because the premise of adding singletonFactories L3 cache is to execute the constructor, the circular dependency of the constructor cannot be resolved

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326175768&siteId=291194637