13
头图

Preface

Spring's circular dependency has been said to be bad, and many people may also be vomiting. However, what many blogs say is still not clear enough to fully express the design purpose of Spring. Only introduced What, but not enough for the introduction of Why.

This article will analyze the design principles of Spring's "three-level cache" step by step from a design perspective, and talk about why it is designed this way.

Bean creation process

Each Bean in Spring is created by a BeanDefinition, after the BeanDefinition is registered. It will traverse the beanDefinitionMap in the BeanFactory and call getBean to initialize all the Beans.

To put it simply, the creation process of a Bean is mainly divided into the following stages:

  1. Instantiate Bean-instantiate Bean through default constructor or constructor injection
  2. Populate Bean-handles the property dependencies of the Bean, which can be injected by Autowired, configured in XML, or manually created dependencies in BeanDefinition (propertyValues)
  3. Initialize Bean-initialize Bean, execute initialization method, execute BeanPostProcessor. This stage is the post-processing of various beans, such as AOP proxy object replacement is in this stage

After completing the above creation process, add the Bean to the cache-singletonObjects, and then look it up in the cache when getting Bean, and create it if it does not exist.

Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

Put aside Spring's source code first, let's take a look at what problems will be encountered in the execution of this creation process

1. Instantiate Bean

The first is the first stage-instantiation, which is to call the constructor of the Bean Class and create an instance. There is nothing to say. As for some of the logic of obtaining the BeanDefinition construction method, it is not the focus of circular dependencies.

2. Populate Bean

The second stage-filling the Bean, its purpose is to find all the Beans referenced by the current Bean, use the BeanFactory to obtain these Beans, and then inject them into the attributes of the current Bean.

Under normal circumstances, there is no problem when there is no circular reference relationship. For example, the populate operation of ABean is currently in progress, and the reference of BBean is found, and it can be completed by going to getBean(BBean) through BeanFactory. Even if the BBean has not been created yet, it can be initialized in getBean. After completion, add the BBean to the created Bean cache-singletonObjects.

ben_cyclic_ref_0 (1).png

Finally, inject the obtained BBean instance into the ABean to complete the populate operation, which looks pretty simple.

At this time, the reference relationship has changed a little, ABean also depends on BBean, and the reference relationship between the two Beans has become mutual reference, as shown in the following figure: ben_cyclic_ref_1.png
Let's take a look at how to execute populate now:

First, initialize the BBean first, and then find the ABean referenced by the Bbean. Now getBean(ABean) is found, and the ABean is not created. Start the creation of the ABean: first instantiate, then populate the ABean, and then discover the ABean when you populate The BBean is referenced, but the BBean has not been created yet, and it does not exist in the Bean cache. In this way, an infinite loop occurs. Two Beans refer to each other, and the populate operation cannot be executed at all.

In fact, solving this problem is also very simple. The key to an infinite loop is that two beans refer to each other. When populates, the other bean is still being created and not created.

Only need to add a intermediate state cache container , used to store those beans that have only executed instantiate but have not yet populated. When to populate the stage, if the full state cache does not exist, look for it again from the intermediate state cache container , thus avoiding the problem of infinite loop.

As shown in the figure below, an intermediate state cache container-earlySingletonObjects is added, which is used to store the Bean that has just been executed instantiate. After the Bean is created, it is deleted from earlySingletonObjects and added to singletonObjects.
ben_cyclic_ref_1 (1).png
back to the above example, if 1609540e035252 finds the BBean reference in the populate phase of ABean, then first search from singletonObjects, if it does not exist, continue to search from earlySingletonObjects, and then inject it into ABean, and then ABean creation is completed (BeanPostProcessor I'll talk about it later). Now add the ABean to the singletonObjects, and then return to the process of creating the BBean. Finally, the returned ABean is injected into the BBean, and the populate operation of the BBean is completed, as shown in the following figure:

ben_cyclic_ref_7 (1).png

The problem of circular dependency is solved so easily. It looks very simple, just adding an intermediate state. But in addition to the instantiate and populate stages, there is the last stage to execute the BeanPostProcessor, which may enhance/replace the original Bean

3. Initialize Bean

This stage is divided into executing the initialization method-initMethod, and executing the BeanPostProcessor (BPP) defined in BeanFactory. There is nothing to say about the implementation of the initialization method. Focus on the implementation of the BeanPostProcessor part.

BeanPostProcessor is the soul interface of Spring, and many extended operations and functions are through this interface, such as AOP. After the populate is completed, Spring will execute all the BeanPostProcessors on the Bean in sequence, and then return the new Bean instance returned by the BeanPostProcessor (may or may not be modified)

//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInitialization
public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
    throws BeansException {

    Object result = existingBean;
    //对当前 Bean 顺序的执行所有的 BeanPostProcessor,并返回
    for (BeanPostProcessor processor : getBeanPostProcessors()) {
        Object current = processor.postProcessBeforeInitialization(result, beanName);
        if (current == null) {
            return result;
        }
        result = current;
    }
    return result;
}

Spring's AOP enhancement function is also completed by BeanPostProcessor. If the Bean has an AOP enhanced configuration, a new Bean will be returned after the BeanPostProcessor is executed, and the last one stored in singletonObjects is also the enhanced Bean

But in our "intermediate state caching" solution above, only the beans that have just been executed and instantiate are stored. If in the above circular dependency example, when populate ABean, because the BBean only completed the instantiation, the that only completed the initialization will be obtained from earlySingletonObjects 1609540e035379 and injected into the ABean.

If the BBean has an AOP configuration, then the injected into the ABean at this time is just an instantiation of the object is not enhanced by AOP. When the BBean executes the BeanPostProcessor, it will create an enhanced BBean instance, and finally added to the singletonObjects is the enhanced BBean instance, not the just instantiated BBean instance

As shown in the figure below, the yellow 1609540e0353e7 injected into the only the initialized ABbean , but the final addition to the singletonObjects is the enhanced ABean instance after the AOP is executed:
ben_cyclic_ref_2 (1).png
Because there is a step of enhancement of BeanPostProcessor after populate, our above solution is invalid. But it is not completely unsolvable. If the enhanced BeanPostProcessor can be executed in advance and then added to the "intermediate state cache container", can it also solve the problem?

But not all beans have AOP (and other returning new objects after executing BPP). It is not appropriate to let all beans execute BeanPostProcessor in advance.

Therefore, a "delayed processing" method can be adopted here, adding a layer of Factory in the middle, and completing the "pre-execution" operation in this Factory.

If the "delayed processing" Factory of a Bean is not called in advance, it will not cause the BeanPostProcessor to be executed in advance. Only in the cyclic dependency scenario, this kind of Bean that is only initialized but not fully created will be called. The factory mode is called delayed processing. If the Factory is not called, the BPP will not be executed in advance.

After instantiate is completed, add this Factory to the "intermediate state cache container"; in this way, when a circular dependency occurs, the originally obtained intermediate state Bean instance will become this Factory. At this time, the execution of this Factory can complete the "pre-execution" BeanPostProcessor" operation, and get the new Bean instance after execution

Now add an ObjectFactory to implement delayed processing:

public interface ObjectFactory<T> {

    T getObject() throws BeansException;

}

Then create a singletonFactories as our new intermediate state cache container, but this container does not store Bean instances, but the implementation code for creating Beans

private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

that "executes in advance" BeanPostProcessor and add it to singletonFactories.

//完成bean 的 instantiate 后
//创建一个对该 Bean 提前执行 BeanPostProcessor 的 ObjectFactory
//最后添加到 singletonFactories 中
addSingletonFactory(beanName, 
                    () -> getEarlyBeanReference(beanName, mbd, bean)
                   );

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            this.singletonFactories.put(beanName, singletonFactory);
            ......
        }
    }
}

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        //提前执行 BeanPostProcessor
        for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
            exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
        }
    }
    return exposedObject;
}

Going back to the above circular dependency example again, if you find a reference to BBean when you populate ABean, then first look for our new delayed processing + pre-executed cache container , but now found It is no longer a BBean instance, but the ObjectFactory of the getEarlyBeanReference we defined above. By calling ObjectFactory.getObject() to obtain the ABean instance that executes the BeanPostProcessor in advance.

As shown in the figure below, when performing populate on ABean, a reference to BBean is found, then directly find the ObjectFactory of BBean from singletonFactories and execute it to obtain a new bean enhanced/replaced by BeanPostProcessor

ben_cyclic_ref_3 (1).png

Now because our intermediate state data has changed from the Bean instance to the ObjectFactory, we need to check whether the singletonFactories has the current Bean after initialization. If so, we need to manually call getObject to obtain the final Bean instance.

Through the "Delayed Execution" + "Early Execution" , this circular dependency problem is finally solved. However, executing the BeanPostProcessor in advance will result in the final execution of the BeanPostProcessor twice. This double-execution problem still needs to be dealt with.

The solution to this problem is fairly simple. Add a cache to those BeanPostProcessors that will replace the original object instance to store the enhanced Bean. Every time the BeanPostProcessor is called, if the cache already exists, it means it has been created. Just go back to the last created one. Spring also designed an interface separately for this purpose, and the name is also very vivid- SmartInstantiationAwareBeanPostProcessor

If the BeanPostProcessor you define will enhance and replace the original Bean instance, you must implement this interface and cache in the implementation to avoid repeated enhancements

It seems that the problem has been solved now, and the earlySingletonObjects designed at the beginning is not needed. The problem can be solved by directly using our intermediate state cache factory-singletonFactories.

But... if the dependency is more complicated, for example, like the following, two attributes in ABean refer to BBean
ben_cyclic_ref_0 (2).png
Then when populate ABean, the refB attribute is processed first; at this time, the ObjectFactory that executes the BeanPostProcessor in advance is found from the singletonFactories of the BBean, and getObject is called to obtain the BBean instance that executes the BeanPostProcessor in advance, and injected into the refB attribute.

When the refB1 attribute is reached, because the BBean is still in a state that has not been created (not existing in singletonObjects), it is still necessary to obtain the ObjectFactory of the BBean and execute the getObject, which causes the BBean to execute the BeanPostProcessor again.

In order to deal with the problem of multiple references, there is still an intermediate state cache container-earlySingletonObjects. However, this cache container is a little different from the earlySingletonObjects mentioned at the beginning; the earlySingletonObjects mentioned at the beginning is to store Bean instances that have only executed the instantiate state, and what we are storing now is after the instantiate is executed, the BeanPostProcessor is executed in advance. Those Beans.

After the BeanPostProcessor is executed in advance, the returned new Bean instance is also added to the cache container of earlySingletonObjects. In this way, even if there are multiple references (multiple getBeans) in the intermediate state, the Bean whose BeanPostProcessor has been executed can also be obtained from earlySingletonObjects without causing the problem of repeated execution.

to sum up

Look at the top of a step by step process to resolve circular dependencies, and ultimately we passed a delay processing cache container, plus a advance the implementation of the intermediate state of the container is completed BeanPostProcessor of on the perfect solution to the problem of circular dependencies

As for the singletonObjects cache container, it is only used to store all created beans, and has little to do with processing circular dependencies.

As for this processing mechanism, it is called "three-level cache"... Different people have different opinions, and Spring does not include the source code/comment (words such as 3-level cache). And the key circular dependency processing is only the "second level" (Delayed Factory + BeanPostProcessor Bean in advance). The so-called "third level" should refer to singletonObjects.

Here is a picture to briefly summarize the core mechanism of processing circular dependencies:
ben_cyclic_ref_5.png
However, if the operation of BeanPostProcessor is executed in advance, is it considered that it breaks the original design? Originally, BeanPostProcessor was executed in the final stage of Bean creation, but now in order to deal with circular dependencies, it has been moved to before populate. Although it is a less elegant design, it is also good for solving circular dependencies.

Although Spring supports circular dependencies (only the attribute dependency method is supported, the construction method dependency is not supported, because instantiation cannot be completed), but in actual projects, this circular dependency relationship is often unreasonable, and it should be designed avoid.

Originality is not easy, unauthorized reprinting is prohibited. If my article is helpful to you, please like/favorite/follow to encourage and support it ❤❤❤❤❤❤

空无
3.3k 声望4.3k 粉丝

坚持原创,专注分享 JAVA、网络、IO、JVM、GC 等技术干货