SpringBoot源码解读与原理分析(二十六)IOC容器的刷新(七)循环依赖的解决方案


前面六节,详细梳理了IOC容器刷新的全部步骤,以及一个重要的后置处理器ConfigurationClassPostProcessor,详见:

SpringBoot源码解读与原理分析(二十)IOC容器的刷新(一)
SpringBoot源码解读与原理分析(二十一)IOC容器的刷新(二)
SpringBoot源码解读与原理分析(二十二)IOC容器的刷新(三)ConfigurationClassPostProcessor
SpringBoot源码解读与原理分析(二十三)IOC容器的刷新(四)
SpringBoot源码解读与原理分析(二十四)IOC容器的刷新(五)
SpringBoot源码解读与原理分析(二十四)IOC容器的刷新(六)

本节是IOC容器刷新阶段的最后一篇,梳理一下循环依赖的解决方案。

7.15 循环依赖的解决方案

IOC容器初始化bean对象的逻辑中可能会遇到bean对象之间循环依赖的问题。当问题出现时,IOC容器内部可以恰当地予以解决。

7.15.1 循环依赖问题的产生

循环依赖,简单理解就是两个或多个bean对象之间的互相引用(互相持有对方的引用)。

例如,人(Person)与猫(Cat)之间相互引用,人养猫,猫依赖人。

SpringFramework会针对不同类型的循环依赖实行不同的处理策略。

7.15.2 循环依赖的解决模型

IOC容器内部对于解决循环依赖主要使用了三级缓存的设计:

  • singletonObjects:一级缓存,存放完全初始化好的bean对象,从这个集合中提取出来的bean对象可以立即返回。
  • earlySingletonObjects:二级缓存,存放创建好但没有初始化属性的bean对象,它用来解决循环依赖。
  • singletonFactories:三级缓存,存放单实例BeanFactory。
  • singletonCurrentlyInCreation:存放正在被创建的bean对象。
代码清单1DefaultSingletonBeanRegistry.java

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

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

/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

/** Names of beans that are currently in creation. */
private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));

由 代码清单1 可知,上述成员均在DefaultListableBeanFactory的父类DefaultSingletonBeanRegistry中,该父类本身是一个单实例bean对象的管理容器。

7.15.3 基于setter/@Autowired的循环依赖

基于setter/@Autowired的循环依赖,是SpringFramework可以解决的一种循环依赖。

7.15.3.1 编写测试代码
代码清单2

public class Person {
    @Autowired
    private Cat cat;
}

public class Cat {
    @Autowired
    private Person person;
}

public class Test03App {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.xiaowd.springboot.aop.test03");
    }
}
7.15.3.2 初始化Cat

由于在字母表中cat比person的首字母靠前,所以IOC容器会先初始化cat对象。

(1)getSingleton中的处理

SpringBoot源码解读与原理分析(二十四)IOC容器的刷新(五) 7.11.2.7 getSingleton控制单实例对象 中,getSingleton方法有一个特殊的步骤:beforeSingletonCreation,这是控制循环依赖的关键步骤。

代码清单3DefaultSingletonBeanRegistry.java

protected void beforeSingletonCreation(String beanName) {
    if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
        throw new BeanCurrentlyInCreationException(beanName);
    }
}

由 代码清单3 可知,beforeSingletonCreation方法会检查当前正在创建的bean对象是否存在于singletonsCurrentlyInCreation集合中。

如果当前正在创建的bean对象的名称存在于该集合中,说明出现了循环依赖问题(同一个对象在一个创建流程中被创建了两次),则抛出BeanCurrentlyInCreationException异常。

如果不存在,则将当前正在创建的bean对象的名称添加到singletonsCurrentlyInCreation集合中。

(2)对象创建完毕后的处理

如果getSingleton方法没有抛出异常,则可以顺利进入createBeandoCreateBean方法。在doCreateBean方法中,createBeanInstance方法执行完毕后,一个空的cat对象被成功创建。

在进入populateBean之前,有一个处理循环依赖的逻辑。

代码清单4AbstractAutowireCapableBeanFactory.java

boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
        isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
        if (logger.isTraceEnabled()) {
            logger.trace("Eagerly caching bean '" + beanName +
                "' to allow for resolving potential circular references");
    }       
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

由 代码清单4 可知,earlySingletonExposure变量由三部分判断结果共同计算产生,分别是:

  • mbd.isSingleton:当前对象是一个单实例bean对象;
  • this.allowCircularReferences:IOC容器本身允许出现循环依赖(默认为true);
  • isSingletonCurrentlyInCreation:正在创建的单实例bean对象名称存在于singletonsCurrentlyInCreation集合中。

前两个条件在当前场景中显然为true;第三个条件,上一步已经将当前正在创建的bean对象添加到singletonsCurrentlyInCreation集合中,因此也为true,所以最终earlySingletonExposure=true,执行if结构中的addSingletonFactory方法。

代码清单5DefaultSingletonBeanRegistry.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);
        }
    }
}

由 代码清单5 可知,addSingletonFactory方法会将当前正在创建的bean对象的名称保存到三级缓存singletonFactories中(注意被包装成了ObjectFactory),并从二级缓存earlySingletonObjects中移除(实际上此时二级缓存并没有cat对象的名称)。

(3)依赖注入时的处理

接下来将进入populateBean方法中,进行属性赋值和依赖注入。

代码清单6AutowiredAnnotationBeanPostProcessor.java

public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
    InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
    try {
        metadata.inject(bean, beanName, pvs);
    } // catch ...
    return pvs;
}

protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
    // ...
    value = resolveFieldValue(field, bean, beanName);
    // ...
}

private Object resolveFieldValue(Field field, Object bean, @Nullable String beanName) {
    // ...
    Object value;
    try {
        value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
    }
    // ...
    return value;
}
代码清单7DefaultListableBeanFactory.java

public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
                                @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {

    // ...
    result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
    // ...
}
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
			@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
    // ...
    instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
    // ...	    
}

public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory)
        throws BeansException {
    return beanFactory.getBean(beanName);
}

由于cat对象中使用@Autowired注解注入了person对象,因此AutowiredAnnotationBeanPostProcessor会在回调postProcessProperties方法时将person对象提取出来并注入到cat对象中。

由 代码清单6、7 可知,依赖注入依次经过AutowiredAnnotationBeanPostProcessor的injectresolveFieldValue方法,以及DefaultListableBeanFactory的resolveDependencydoResolveDependencyresolveCandidate方法。当执行最后一个方法时,使用BeanFactory的getBean方法触发person对象的初始化全流程(此时person对象并未初始化)。

7.12.3.3 初始化Person

创建person对象的过程与创建cat类似,都是执行getBeandoGetBean,其中包含geiSingleton处理,以及对象创建完毕后将person对象包装成ObjectFactory放入三级缓存singletonFactories中。

到了依赖注入环节,由于person对象中使用@Autowired注解注入了cat对象,因此AutowiredAnnotationBeanPostProcessor会从BeanFactory中获取cat对象并注入。

(1)再次获取cat对象

再次获取cat对象时执行的方法依然是getBeandoGetBean,但在doGetBean方法中调用getSingleton方法时,调用的是另一个重载方法。

代码清单8DefaultSingletonBeanRegistry.java

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 从一级缓存获取对象
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        // 一级缓存没有对象,且对象正在创建,则从二级缓存中取对象
        singletonObject = this.earlySingletonObjects.get(beanName);
        if (singletonObject == null && allowEarlyReference) {
            synchronized (this.singletonObjects) {
                // 二级缓存中没对象,且允许创建早期引用,则再从一级缓存中取对象
                singletonObject = this.singletonObjects.get(beanName);
                if (singletonObject == null) {
                    // 一级缓存中没对象,则从二级缓存中取对象
                    singletonObject = this.earlySingletonObjects.get(beanName);
                    if (singletonObject == null) {
                        // 二级缓存中没对象,则从三级缓存中取对象
                        ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                        if (singletonFactory != null) {
                            // 三级缓存中有对象,将bean对象放入二级缓存,并从三级缓存中移除
                            singletonObject = singletonFactory.getObject();
                            this.earlySingletonObjects.put(beanName, singletonObject);
                            this.singletonFactories.remove(beanName);
                        }
                    }
                }
            }
        }
    }
    return singletonObject;
}

由 代码清单8 可知,如果当前获取的bean对象正在创建,并且二级缓存earlySingletonObjects没有该bean对象,三级缓存singletonFactories有该bean对象,则说明当前获取的bean对象是一个没有完成依赖注入的不完全对象。

即便当前cat对象是一个不完全的对象,但也是一个存在于IOC容器的真实对象,不影响注入,因此getSingleton方法在判断条件成立后,将bean对象放入二级缓存,并从三级缓存中移除。

(2)person对象初始化完成后的处理

执行完 代码清单8 的getSingleton方法后,获取到了cat对象,并注入到person对象中,后续再进行person对象的初始化逻辑。

上述步骤都执行完后,再次调用另一个重载的getSingleton方法。

代码清单9DefaultSingletonBeanRegistry.java

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    // ...
            try {
                singletonObject = singletonFactory.getObject();
                newSingleton = true;
            } // catch ...
            } finally {
                if (recordSuppressedExceptions) {
                    this.suppressedExceptions = null;
                }
                afterSingletonCreation(beanName);
            }
            if (newSingleton) {
                addSingleton(beanName, singletonObject);
            }
        }
        return singletonObject;
    }
}

protected void afterSingletonCreation(String beanName) {
    if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
        throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
    }
}

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);
    }
}

由 代码清单9 可知,afterSingletonCreation方法的作用是将当前正在获取的bean对象名称从singletonsCurrentlyInCreation中移除,代表当前环节中该bean对象未正在创建;

addSingleton方法的作用是将创建好的bean对象放入一级缓存singletonObjects中,且从二级缓存earlySingletonObjects和三级缓存singletonFactories中移除,并记录已经创建的单实例bean对象。

至此,一个person对象创建流程结束。

7.12.3.4 回到Cat的创建流程

person对象创建完成后,此时cat对象的依赖注入工作尚未完成。此处会将完全创建好的person对象进行依赖注入。注入完成后,代表Cat与Person循环依赖的场景处理完毕。

后续的动作与person对象一致,最终会将cat对象放入一级缓存,从其他的缓存中移除,从而完成cat对象的创建。

7.12.3.5 小结

基于setter方法或@Autowired属性注入的循环依赖,IOC容器的解决流程如下:

(1)创建bean对象之前,将该bean对象的名称放入“正在创建的bean对象”集合singletonCurrentlyInCreation中。
(2)doCreateBean方法中的createBeanInstance方法执行完毕后,会将当前bean对象包装成ObjectFactory放入三级缓存singletonFactories中。
(3)对bean对象进行属性赋值和依赖注入时,触发被循环依赖的bean对象的初始化流程。
(4)被循环依赖的bean对象创建时,会检查三级缓存singletonFactories中是否存在、且二级缓存earlySingletonObjects中是否不存在该bean对象。如果三级缓存中存在且二级缓存中不存在该bean对象,则会将三级缓存中的bean对象移入二级缓存中,并进行依赖注入。
(5)被循环依赖的bean对象创建完毕后,会将该对象放入一级缓存singletonObjects中,并从其他缓存中移除。
(6)所有循环依赖的bean对象均注入完毕后,一个循环依赖的处理流程结束。

7.15.4 基于构造方法的循环依赖

基于构造方法的循环依赖,是IOC容器无法予以处理的一种类型,只能抛出异常。

假设还以cat和person对象为例:

(1)IOC容器首先创建cat对象,由于调用cat的构造方法需要依赖person对象,从而引发person对象的创建。
(2)IOC容器创建person对象,由于调用person的构造方法需要依赖cat对象,此时cat对象还未创建出来,因而再一次引发cat对象的创建。
(3)IOC容器第二次创建cat对象时,由于第一次创建cat对象就在singletonCurrentlyInCreation集合中存放了“cat”名称,因此第二次创建cat对象时singletonCurrentlyInCreation集合中已存在“cat”名称,从而抛出BeanCurrentlyInCreationException异常,表示出现了不可解决的循环依赖。

7.15.5 基于原型Bean的循环依赖

基于原型Bean的循环依赖,也是IOC容器无法予以处理的一种类型。

以cat和person对象为例:

(1)IOC容器首先创建cat对象,之后进行person对象的依赖注入,由于person被定义为原型Bean,触发person对象的创建。
(2)IOC容器创建person对象,之后进行cat对象的依赖注入,由于cat对象也被定义为原型Bean,触发cat对象的创建。
(3)IOC容器第二次创建cat对象时,由于第一次创建cat对象就在原型Bean对象名称的集合prototypesCurrentlyInCreation中存放了“cat”名称,因此第二次创建cat对象时prototypesCurrentlyInCreation集合中已存在“cat”名称,从而抛出BeanCurrentlyInCreationException异常,表示出现了不可解决的循环依赖。

7.15.6 引入AOP的额外设计

在 7.15.3节 中提到,getSingleton方法会将三级缓存中的bean对象放入二级缓存中,三级缓存中存放的是被封装过的ObjectFactory对象,而二级缓存中存放的是真正的bean对象,为什么会有ObjectFactory对象到bean对象之间的过渡呢?

已知的是,在bean对象创建完成后,IOC容器会指派后置处理器BeanPostProcessor对需要进行AOP增强的bean对象进行代理对象的创建。

原始的目标对象和被AOP增强后的代理对象本质上是两个完全不同的对象,IOC容器为了确保最终注入的是AOP增强后的代理对象而不是原始的目标对象,会在ObjectFactory对象到bean对象的过渡期间进行额外的检查,该环节的检查会提前创建代理对象,并替换原始对象。

代码清单10AbstractAutowireCapableBeanFactory.java

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) 
    throws BeanCreationException {
    // ...
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
        isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
        // logger ...
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }
    // ...
}

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;
}

由 代码清单10 可知,getEarlyBeanReference方法的实现是回调所有SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference方法,该方法可以获取单实例bean对象的引用,也正是通过该方法IOC容器可以将一个普通bean对象转化为被AOP增强的代理对象。

所有实现AOP增强的后置处理器都继承自AbstractAutoProxyCreator,而它本身实现了SmartInstantiationAwareBeanPostProcessor接口,内部自然有getEarlyBeanReference方法。

代码清单11AbstractAutoProxyCreator.java

@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    this.earlyProxyReferences.put(cacheKey, bean);
    // 必要时创建代理对象
    return wrapIfNecessary(bean, beanName, cacheKey);
}

由 代码清单11 可知,如果当前正在创建的bean对象的确需要创建代理对象,则会先行创建代理对象,并替换原始对象。由此,解释了为什么IOC容器解决循环依赖使用三级缓存而不是二级。

7.16 小结

第7章到此就梳理完毕了,本章的主题是:IOC容器的刷新。回顾一下本章的梳理的内容:

(二十)IOC容器的刷新(一):7.1-7.3
(二十一)IOC容器的刷新(二):7.4-7.5
(二十二)IOC容器的刷新(三):ConfigurationClassPostProcessor
(二十三)IOC容器的刷新(四):7.6-7.10
(二十四)IOC容器的刷新(五):7.11
(二十五)IOC容器的刷新(六):7.12-7.13,IOC容器刷新过程中涉及的扩展点
(二十六)IOC容器的刷新(七):循环依赖的解决方案

更多内容请查阅分类专栏:SpringBoot源码解读与原理分析

第8章主要梳理:嵌入式Web容器。主要内容包括:

  • 嵌入式Tomcat容器简介;
  • Tomcat的整体框架与核心工作流程;
  • 嵌入式Web容器的模型设计;
  • 嵌入式Web容器的初始化时机;
  • 嵌入式Tomcat的回调启动。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

灰色孤星A

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值