How does Spring solve circular dependencies?

In the last article, we analyzed in detail doCreateBean()the second step: instantiating the bean, doCreateBean()and the fourth step " circular dependency processing ", which is the populateBean()method, is then analyzed in this article.

First review the main process of bean loading:

  1. If it is a singleton mode, get the BeanWrapper instance object from the factoryBeanInstanceCache cache and delete the cache
  2. call createBeanInstance()instantiated bean
  3. post processing
  4. Circular dependency handling in singleton pattern
  5. property fill
  6. Initialize the bean instance object
  7. dependency check
  8. Register DisposableBean

In this chapter, we mainly analyze the fourth step:

1. What is circular dependency?

A circular dependency is actually a circular reference, that is, two or more beans refer to each other, eventually forming a closed loop, such as A depends on B, B depends on C, and C depends on A. As shown below:

The circular dependency in Spring is actually an infinite loop process. When initializing A, it finds that it depends on B, then it will initialize B, and then find that B depends on C, run to initialize C, and find the dependency when initializing C. If A is found, A will be initialized again, and the loop will never exit unless there is a termination condition.

Generally speaking, there are two cases of Spring circular dependencies:

Circular dependencies for constructors. Circular dependency of the field property. For the circular dependency of the constructor, Spring cannot solve it. It can only throw BeanCurrentlyInCreationExceptionan exception to represent the circular dependency, so the following analysis is based on the circular dependency of the field attribute.

As mentioned in the Bean Loading of Spring Ioc Source Analysis (3): Bean Creation of Each Scope, Spring only solves the circular dependency whose scope is singleton. For beans whose scope is prototype, Spring cannot resolve them and throws BeanCurrentlyInCreationException directly.

Why doesn't Spring handle prototype beans? In fact, if you understand how Spring solves the circular dependency of the singleton bean, you will understand. Let's leave a question here, let's first look at how Spring solves the circular dependency of the singleton bean.

2. Solve singleton circular dependencies

In the doGetBean()method , when we obtain the Singleton Bean according to the BeanName, it will be obtained from the cache first.
code show as below:

//DefaultSingletonBeanRegistry.java

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 从一级缓存缓存 singletonObjects 中加载 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();
                    // 添加到二级缓存
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    // 从三级缓存中删除
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

The three key variables involved in this code are three levels of cache, which are defined as follows:

	/** Cache of singleton objects: bean name --> bean instance */
	//单例bean的缓存 一级缓存
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

	/** Cache of singleton factories: bean name --> ObjectFactory */
	//单例对象工厂缓存 三级缓存
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

	/** Cache of early singleton objects: bean name --> bean instance */
	//预加载单例bean缓存 二级缓存
	//存放的 bean 不一定是完整的
	private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

getSingleton()The logic is relatively clear:

  • singletonObjectsFirst, try to get the singleton bean from the first level cache .

  • earlySingletonObjectsIf it cannot be obtained, the singleton bean is obtained from the second-level cache .

  • singletonFactoriesIf it still cannot be obtained, obtain the singleton BeanFactory from the third-level cache .

  • Finally, if the BeanFactory is obtained from the third-level cache, getObject()the bean is stored in the second-level cache and the third-level cache of the bean is deleted.

2.1, L3 cache

There may be some doubts here. How can these three caches solve the singleton circular dependency?
Don't worry, let's analyze the code to get the cache now, and then look at the code to store the cache. In the method of AbstractAutowireCapableBeanFactory , there is such a piece of code:doCreateBean()

// 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 中
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

This code is where the L3 cache is put singletonFactories. Its core logic is to add the bean to the L3 cache when the following three conditions are met:

  • singleton
  • allow circular dependencies
  • The current singleton bean is being created

addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory)method, the code is as follows:

// 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 the three-level cache of singletonFactories is the key to solving the circular dependency of Spring Bean. At the same time, this code occurs after the createBeanInstance(...)method , which means that the bean has actually been created, but it is not perfect (no property filling and initialization), but it is enough for other objects that depend on it (already have The memory address can be located according to the object reference to the object in the heap) and can be recognized.

2.2. Level 1 cache

At this point, we found that the values ​​in the third-level cache singletonFactories and the second-level cache earlySingletonObjects have sources. Where is the first-level cache set? In the class DefaultSingletonBeanRegistry, you can find this addSingleton(String beanName, Object singletonObject)method , the code is 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);
	}
}

This method is in the #doGetBean(...) method. When dealing with different scopes, if it is called by a singleton, as shown in the following figure:

That is to say, there are complete beans in the first-level cache.

summary:

  • There are complete beans in the first-level cache, which are put when a bean is completely created.
  • The third-level cache is an incomplete BeanFactory, which is put when a Bean is new (no attribute filling, initialization)
  • The second-level cache is an easy-to-use processing of the third-level cache, but it is just a getObject()method to remove the Bean from the BeanFactory of the third-level cache.
总结

Now let's review Spring's solution to singleton circular dependencies:

  • When Spring creates a bean, it does not wait for it to be completely completed, but exposes the ObjectFactory of the bean being created in advance (ie, adds it to the singletonFactories L3 cache) during the creation process.

  • In this way, once the next bean is created and needs to depend on the bean, it will be obtained from the third-level cache.

Take a chestnut : For
example, if our team wants to sign up for an event, you don’t have to come up to fill in all your birthday, gender, family information, etc. You only need to report your name first, count the number of people, and then slowly improve you personal information.

Core idea: Expose in advance, use first

Finally, let's describe the process of resolving the above circular dependency with Spring:

  • First, A completes the first step of initialization and exposes itself in advance (through the third-level cache to expose itself in advance). During initialization, it finds that it depends on object B, and it will try get(B) at this time. At this time, it is found that B has not been created

  • Then B goes through the creation process. When B is initialized, it also finds that it depends on C, and C is not created.

  • At this time, C starts the initialization process again, but in the process of initialization, it finds that it depends on A, so it tries to get(A). At this time, since A has been added to the cache (the third-level cache singletonFactories), it is exposed in advance through ObjectFactory, so it can pass ObjectFactory#getObject()method to get the A object, C successfully completes the initialization after getting the A object, and then adds itself to the first-level cache

  • Back to B, B can also get the C object and complete the initialization, and A can successfully get B to complete the initialization. At this point, the entire link has completed the initialization process.

Finally, why doesn't the multi-instance pattern resolve circular dependencies?
Because each new() Bean in the multi-instance mode is not one, if it is stored in the cache in this way, it becomes a singleton.

Reference :
http://cmsblogs.com/?p=todo (Brother Xiaoming)

{{o.name}}
{{m.name}}

Guess you like

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