[Interview] How to solve the circular dependency problem in spring?

foreword

think:

  • What is a circular dependency?
  • How does Spring solve circular dependencies
  • Scenarios that Spring cannot solve for circular dependencies

1. What is circular dependency?

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

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

Circular dependency scenarios in Spring include:

  • Constructor Circular Dependency
  • Circular dependencies for field attributes.

Thinking: In the process of coding, sometimes there will be such demands. At this time, which object should the program create first?

2. How does Spring solve 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 generation of an object in spring requires the following steps

  • createBeanInstance: instantiation, in fact, is to call the constructor of the object to instantiate the object
  • populateBean: fill in attributes, this step is mainly to fill in the dependency properties of multiple beans
  • initializeBean: Call the init method in spring xml.
/**
     * 实际创建指定的bean。 此时,预创建处理已经发生,
     * 例如 检查{@code postProcessBeforeInstantiation}回调。
     * 区分默认bean实例化、使用工厂方法和自动装配构造函数。
     */
    protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {
    
    
        // Instantiate the bean.
        BeanWrapper instanceWrapper = null;
        .....
        
        if (instanceWrapper == null) {
    
    
            instanceWrapper = createBeanInstance(beanName, mbd, args);
        }
        .....
        // Eagerly cache singletons to be able to resolve circular references
        // even when triggered by lifecycle interfaces like BeanFactoryAware.
        boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                isSingletonCurrentlyInCreation(beanName));
        if (earlySingletonExposure) {
    
    
            if (logger.isDebugEnabled()) {
    
    
                logger.debug("Eagerly caching bean '" + beanName +
                        "' to allow for resolving potential circular references");
            }
            //添加到三级缓存中
            addSingletonFactory(beanName, new ObjectFactory<Object>() {
    
    
                @Override
                public Object getObject() throws BeansException {
    
    
                    return getEarlyBeanReference(beanName, mbd, bean);
                }
            });
        }
 
        // Initialize the bean instance.
        Object exposedObject = bean;
        try {
    
    
            //填充依赖的bean实例。
            populateBean(beanName, mbd, instanceWrapper);
            if (exposedObject != null) {
    
    
                //调用spring xml中的init 方法。
                exposedObject = initializeBean(beanName, exposedObject, mbd);
            }
        }
        ......
        return exposedObject;
    }

In fact, we simply think about it and find that the problem of circular dependency is mainly in two steps (1) and (2), that is, the circular dependency of the constructor in (1) and the circular dependency of the field in (2).

Note: createBeanInstance(...) This step will call the constructor

/**
     *使用适当的实例化策略为指定的bean创建一个新实例:
     * 工厂方法,构造函数自动装配或简单实例化。
     * @return BeanWrapper for the new instance
     */
    protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, Object[] args) {
    
    
		
        .......
        // Need to determine the constructor...(确定构造函数)
        Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
        if (ctors != null ||
                mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR ||
                mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args))  {
    
    
            return autowireConstructor(beanName, mbd, ctors, args);
        }
 
        // No special handling: simply use no-arg constructor.(使用默认无参构造器,
        //编程时候要求尽量保留无参构造器,因为你不知道哪个框架在哪会用到)
        return instantiateBean(beanName, mbd);
    }

3. How to solve it?

Then we should start from the initialization process to solve the circular reference. For a singleton, there is one and only one object in the entire life cycle of the Spring container, so it is easy to think that this object should be stored in the Cache.Spring为了解决单例的循环依赖问题,使用了三级缓存。

/** Cache of singleton objects: bean name --> bean instance */
    //单例对象的cache
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);
 
    /** Cache of singleton factories: bean name --> ObjectFactory */
    //单例对象工厂的cache
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
 
    /** Cache of early singleton objects: bean name --> bean instance */
    //提前暴光的单例对象的Cache
    private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);

step:

  1. Spring starts with 一级缓存singletonObjects中获取.

  2. If you can't get it, and the object is being created, just start again 二级缓存earlySingletonObjects中获取.

  3. If you still can't get it and allow singletonFactories to get it through getObject(), just从三级缓存singletonFactory.getObject()(三级缓存)获取.

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

/**
     * 返回指定名称(原始)单例对象。
     * 检查已经实例化的单例,也允许对当前创建的单例对象的早期引用(用于解决循环依赖)
     * 
     * @return the registered singleton object, or {@code null} if none found
     */
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    
    
        //Spring首先从一级缓存singletonObjects中获取。
        Object singletonObject = this.singletonObjects.get(beanName);
 
        //isSingletonCurrentlyInCreation()判断当前单例bean是否正在创建中,也就是没有初始化
        //完成(比如A的构造器依赖了B对象所以得先去创建B对象, 或则在A的populateBean过程中依
        //赖了B对象,得先去创建B对象,这时的A就是处于创建中的状态。)
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
    
    
            synchronized (this.singletonObjects) {
    
    
 
                //尝试从二级缓存earlySingletonObjects中获取。
                singletonObject = this.earlySingletonObjects.get(beanName);
 
                //allowEarlyReference 是否允许从singletonFactories中通过getObject拿到对象
                if (singletonObject == null && allowEarlyReference) {
    
    
                    //尝试从三级缓存singletonFactory.getObject()(三级缓存)获取
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
 
                    //如果从三级缓存中获取到
                    // 则从singletonFactories中移除,并放入earlySingletonObjects中。
                    // 其实也就是从三级缓存移动到了二级缓存。
                    if (singletonFactory != null) {
    
    
                        singletonObject = singletonFactory.getObject();
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return (singletonObject != NULL_OBJECT ? singletonObject : null);
    }

From the analysis of the three-level cache, the trick of Spring to solve the circular dependency lies in the three-level cache of singletonFactories. The type of this cache is ObjectFactory, defined as follows:

/**
 * 定义一个可以返回对象实例的工厂
 * @param <T>
 */
public interface ObjectFactory<T> {
    
    
    T getObject() throws BeansException;
}

The interface is referenced in this method, which is called in the instantiation step of the doCreateBean(...) method

/**
     *  添加一个构建指定单例对象的单例工厂
     *  紧急注册单例对象,用于解决解决循环依赖问题
     * To be called for eager registration of singletons, e.g. to be able to
     * resolve circular references.
     */
    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);
            }
        }
    }

Here is the key to solving the circular dependency, this code occurs in the doCreateBean(…) method createBeanInstance之后,也就是说单例对象此时已经被创建出来(调用了构造器). This object has been produced, although it is not perfect (the second and third steps of initialization have not yet been performed), but it has been recognized (the object in the heap can be located according to the object reference), so Spring At this time, this object is exposed in advance to let everyone know and use it.

What are the benefits of doing this?
Let's analyze the circular dependency of "A certain field or setter of A depends on the instance object of B, and at the same time a certain field or setter of B depends on the instance object of A". A has completed the process first 初始化的第一步,并且将自己提前曝光到singletonFactories, and at this time proceeds to the second step of initialization, and finds that he is dependent on object B, so he tries to get(B) at this time, and finds that B has not been created, so he follows the create process, and B is in the first step of initialization At that time, I found that I relied on object A, so I tried get(A), tried the first-level cache singletonObjects (certainly not, because A has not been fully initialized), tried the second-level cache earlySingletonObjects (nor), tried the third-level cache singletonFactories

Since A exposes itself in advance through ObjectFactory, B can get the A object through ObjectFactory. 2, 3, after fully initialized, put itself into the first-level cache singletonObjects. Returning to A at this time, A can get the object of B and successfully completes its own initialization phases 2 and 3. Finally, A also completes the initialization and enters the first-level cache singletonObjects, and more fortunately, because B got The object reference of A, so the A object that B now holds has completed the initialization. ( 简单来说,就是spring创造了一个 循环依赖的结束点标识)

4. What kind of circular dependencies cannot be handled?

(1) Because the premise of adding singletonFactories three-level cache is to execute the constructor to create semi-finished objects, so the circular dependency of the constructor cannot be resolved. Therefore, Spring cannot solve the problem of "A's construction method relies on the instance object of B, and at the same time, the construction method of B relies on the instance object of A"!

(2) spring 不支持原型(prototype)bean属性注入循环依赖, unlike the constructor injecting circular dependency, which will report an error when creating the spring container context, it will throw an exception when the user executes code such as context.getBean().因为对于原型bean,spring容器只有在需要时才会实例化,初始化它。

Because spring容器不缓存prototype类型的bean,使得无法提前暴露出一个创建中的bean. When the spring container obtains a bean of the prototype type, if it detects that the current thread is already processing the bean due to the existence of the ring, it will throw an exception. core code

public abstract class AbstractBeanFactory{
    
    
	/** Names of beans that are currently in creation */
	private final ThreadLocal<Object> prototypesCurrentlyInCreation =
			new NamedThreadLocal<>("Prototype beans currently in creation");
			
	protected boolean isPrototypeCurrentlyInCreation(String beanName) {
    
    
		Object curVal = this.prototypesCurrentlyInCreation.get();
		// 如果beanName已经存在于正在处理中的prototype类型的bean集中,后面会抛出异常
		return (curVal != null &&
				(curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
	}
}

5. Summary:

When learning a new knowledge or skill, we should not only pay attention to why it exists (value), its appearance helps us, but we should also, 解决了什么问题,带来了怎样的便利,under 思考它的不足what circumstances (scenario) it cannot be used, then this 个时候我们应该怎么办, don’t be a problem Make yourself appear anxious when it does show up.

Guess you like

Origin blog.csdn.net/u011397981/article/details/130034359
Recommended