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

您所在的位置:网站首页 ioc如何解决循环依赖 SpringBoot源码解读与原理分析(二十六)IOC容器的刷新(七)循环依赖的解决方案

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

2024-07-12 00:53| 来源: 网络整理| 查看: 265

文章目录 7.15 循环依赖的解决方案7.15.1 循环依赖问题的产生7.15.2 循环依赖的解决模型7.15.3 基于setter/@Autowired的循环依赖7.15.3.1 编写测试代码7.15.3.2 初始化Cat(1)getSingleton中的处理(2)对象创建完毕后的处理(3)依赖注入时的处理 7.12.3.3 初始化Person(1)再次获取cat对象(2)person对象初始化完成后的处理 7.12.3.4 回到Cat的创建流程7.12.3.5 小结 7.15.4 基于构造方法的循环依赖7.15.5 基于原型Bean的循环依赖7.15.6 引入AOP的额外设计 7.16 小结 前面六节,详细梳理了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对象。 代码清单1:DefaultSingletonBeanRegistry.java /** Cache of singleton objects: bean name to bean instance. */ private final Map singletonObjects = new ConcurrentHashMap(256); /** Cache of singleton factories: bean name to ObjectFactory. */ private final Map singletonFactories = new HashMap(16); /** Cache of early singleton objects: bean name to bean instance. */ private final Map earlySingletonObjects = new ConcurrentHashMap(16); /** Names of beans that are currently in creation. */ private final Set 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,这是控制循环依赖的关键步骤。

代码清单3:DefaultSingletonBeanRegistry.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方法没有抛出异常,则可以顺利进入createBean→doCreateBean方法。在doCreateBean方法中,createBeanInstance方法执行完毕后,一个空的cat对象被成功创建。

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

代码清单4:AbstractAutowireCapableBeanFactory.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方法。

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

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

(3)依赖注入时的处理

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

代码清单6:AutowiredAnnotationBeanPostProcessor.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; } 代码清单7:DefaultListableBeanFactory.java public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName, @Nullable Set autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException { // ... result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter); // ... } public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName, @Nullable Set 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的inject、resolveFieldValue方法,以及DefaultListableBeanFactory的resolveDependency、doResolveDependency、resolveCandidate方法。当执行最后一个方法时,使用BeanFactory的getBean方法触发person对象的初始化全流程(此时person对象并未初始化)。

7.12.3.3 初始化Person

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

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

(1)再次获取cat对象

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

代码清单8:DefaultSingletonBeanRegistry.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方法。

代码清单9:DefaultSingletonBeanRegistry.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对象的过渡期间进行额外的检查,该环节的检查会提前创建代理对象,并替换原始对象。

代码清单10:AbstractAutowireCapableBeanFactory.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方法。

代码清单11:AbstractAutoProxyCreator.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的回调启动。


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3