关于c#:浅谈-Spring-如何解决-Bean-的循环依赖问题

37次阅读

共计 9272 个字符,预计需要花费 24 分钟才能阅读完成。

  1. 什么是循环依赖?
    艰深来讲,循环依赖指的是一个实例或多个实例存在相互依赖的关系(类之间循环嵌套援用)。
    举个例子
    public class AService {
    private BService bService;
    }

public class BService {

private AService aService;

}
复制代码
上述例子中 AService 依赖了 BService,BService 也依赖了 AService,这就是两个对象之间的相互依赖。当然循环依赖还包含 本身依赖、多个实例之间相互依赖。

失常运行下面的代码调用 AService 对象并不会呈现问题,也就是说一般对象就算呈现循环依赖也不会存在问题,因为对象之间存在依赖关系是很常见的,那么为什么被 Spring 容器治理后的对象会呈现循环依赖问题呢?

  1. Spring Bean 的循环依赖问题
    被 Spring 容器治理的对象叫做 Bean,为什么 Bean 会存在循环依赖问题呢?
    想要理解 Bean 的循环依赖问题,首先须要理解 Bean 是如何创立的。
    2.1 Bean 的创立步骤
    为了能更好的展现呈现循环依赖问题的环节,所以这里的 Bean 创立步骤做了简化:

在创立 Bean 之前,Spring 会通过扫描获取 BeanDefinition。
BeanDefinition 就绪后会读取 BeanDefinition 中所对应的 class 来加载类。
实例化阶段:依据构造函数来实现实例化(未属性注入以及初始化的对象 这里简称为 原始对象)
属性注入阶段:对 Bean 的属性进行依赖注入(这里就是产生循环依赖问题的环节)
如果 Bean 的某个办法有 AOP 操作,则须要依据原始对象生成代理对象。
最初把代理对象放入单例池(一级缓存 singletonObjects)中。

下面的步骤次要是为了突出循环依赖问题,如果想理解 Bean 的残缺生命周期能够看这一篇文章:浅谈 Spring Bean 的生命周期 – 掘金 (juejin.cn)
两点阐明:

下面的 Bean 创立步骤是对于 单例(singleton)作用域的 Bean。
Spring 的 AOP 代理就是作为 BeanPostProcessor 实现的,而 BeanPostProcessor 是产生在属性注入阶段后的,所以 AOP 是在 属性注入 后执行的。

2.2 为什么 Spring Bean 会产生循环依赖问题?
通过下面的 Bean 创立步骤可知:实例化 Bean 后会进行 属性注入(依赖注入)
如下面的 AService 和 BService 的依赖关系,当 AService 创立时,会先对 AService 进行实例化生成一个原始对象,而后在进行属性注入时发现了须要 BService 对应的 Bean,此时就会去为 BService 进行创立,在 BService 实例化后生成一个原始对象后进行属性注入,此时会发现也须要 AService 对应的 Bean。

这样就会造成 AService 和 BService 的 Bean 都无奈创立,就会产生 循环依赖 问题。

2.3 三大循环依赖问题场景
Spring 并不能解决所有 Bean 的循环依赖问题,接下来通过例子来看看哪些场景下的循环依赖问题是不能被解决的。
AService 类
/**

  • @author 单程车票
    */

public class AService {

private BService bService;

public AService() {}

public AService(BService bService) {this.bService = bService;}

public BService getbService() {return bService;}

public void setbService(BService bService) {this.bService = bService;}

}
复制代码
BService 类
/**

  • @author 单程车票
    */

public class BService {

private AService aService;

public BService() {}

public BService(AService aService) {this.aService = aService;}

public AService getaService() {return aService;}

public void setaService(AService aService) {this.aService = aService;}

}
复制代码
测试类
/**

  • 测试类
  • @author 单程车票
    */

public class Application {

public static void main(String[] args) {ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application-context.xml");
    AService aService = (AService) applicationContext.getBean("aService");
    System.out.println("执行胜利,获取 AService 对象为:" + aService);
}

}
复制代码

单例作用域下的 Setter 办法注入 / field 属性注入 呈现的循环依赖

application-context.xml 配置文件
应用 property 标签也就是 Setter 办法 进行属性注入。
<?xml version=”1.0″ encoding=”UTF-8″?>
<beans xmlns=”http://www.springframework.org/schema/beans”

   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="aService" class="com.xqsr.springtest.service.AService">
    <property name="bService" ref="bService"/>
</bean>

<bean id="bService" class="com.xqsr.springtest.service.BService">
    <property name="aService" ref="aService"/>
</bean>

</beans>
复制代码
运行后果

能够看到 Setter 办法注入形式 在 Spring 中是不会产生循环依赖问题的,这次要是靠 三级缓存 机制(下文会具体阐明)。

单例作用域下的 结构器注入 呈现的循环依赖

application-context.xml 配置文件
<?xml version=”1.0″ encoding=”UTF-8″?>
<beans xmlns=”http://www.springframework.org/schema/beans”

   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="aService" class="com.xqsr.springtest.service.AService">
    <constructor-arg name="bService" ref="bService"/>
</bean>

<bean id="bService" class="com.xqsr.springtest.service.BService">
    <constructor-arg name="aService" ref="aService"/>
</bean>

</beans>
复制代码
运行后果
抛出 BeanCurrentlyInCreationException 异样,阐明 Spring 无奈解决 结构器注入 呈现的循环依赖问题。

起因:因为 结构器注入 产生在 实例化阶段,而 Spring 解决循环依赖问题依附的 三级缓存 在 属性注入阶段,也就是说调用构造函数时还未能放入三级缓存中,所以无奈解决 结构器注入 的循环依赖问题。

原型 作用域下的属性注入呈现的循环依赖问题

application-context.xml 配置文件
<?xml version=”1.0″ encoding=”UTF-8″?>
<beans xmlns=”http://www.springframework.org/schema/beans”

   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="aService" class="com.xqsr.springtest.service.AService" scope="prototype">
    <property name="bService" ref="bService"/>
</bean>

<bean id="bService" class="com.xqsr.springtest.service.BService" scope="prototype">
    <property name="aService" ref="aService"/>
</bean>

</beans>
复制代码
运行后果
同样抛出 BeanCurrentlyInCreationException 异样,阐明 Spring 无奈解决 原型作用域 呈现的循环依赖问题。

起因:因为 Spring 不会缓存 原型 作用域的 Bean,而 Spring 依附 缓存 来解决循环依赖问题,所以 Spring 无奈解决 原型 作用域的 Bean。

  1. Spring 如何解决循环依赖问题?
    通过上文的内容能理解到 Spring 为什么会产生循环依赖问题 以及 Spring 能解决什么场景下的循环依赖问题。
    上文中也有提到过 Spring 是靠 三级缓存 来解决循环依赖问题的,接下来理解一下 什么是三级缓存 以及 解决循环依赖问题的具体流程。
    3.1 三级缓存是什么?
    /* 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 HashMap<>(16);
复制代码
三级缓存分为:

一级缓存(singletonObjects):缓存的是曾经实例化、属性注入、初始化后的 Bean 对象。
二级缓存(earlySingletonObjects):缓存的是实例化后,但未属性注入、初始化的 Bean 对象(用于提前裸露 Bean)。
三级缓存(singletonFactories):缓存的是一个 ObjectFactory,次要作用是生成原始对象进行 AOP 操作后的代理对象(这一级缓存次要用于解决 AOP 问题,后续文章中解说)。

3.2 为什么缓存能够解决循环依赖问题?
(留神这里只是为了阐明缓存能够解决循环依赖问题,然而 Spring 实际上并不是这样做的)
上文中能够看到 AService 和 BService 的循环依赖问题是因为 AService 的创立 须要 BService 的注入,BService 的注入 须要 BService 的创立,BService 的创立 须要 AService 的注入,AService 的注入 须要 AService 的创立,从而造成的环形调用。
想要突破这一环形,只须要减少一个 缓存 来寄存 原始对象 即可。
在创立 AService 时,实例化后将 原始对象 寄存到缓存中(提前裸露),而后依赖注入时发现须要 BService,便会去创立 BService,实例化后同样将 原始对象 寄存到缓存中,而后依赖注入时发现须要 AService 便会从缓存中取出并注入,这样 BService 就实现了创立,随后 AService 也就能实现属性注入,最初也实现创立。这样就突破了环形调用,防止循环依赖问题。

3.3 为什么还须要第三级缓存?
通过下面的剖析能够发现只须要一个寄存 原始对象 的缓存就能够解决循环依赖问题,也就是说只有二级缓存(earlySingletonObjects)就够了,那么为什么 Spring 还设置了三级缓存(singletonFactories)呢?
其实 第三级缓存(singletonFactories)是为了解决 Spring 的 AOP 的。
如下面的例子如果 AService 中办法没有应用 AOP 操作,会发现 BService 注入的 原始对象 与最初 AService 实现创立后的最终对象是同一个对象。
如果 AService 办法中有 AOP 操作,Bean 的创立会如下图:

所以如果 AService 办法中有 AOP 操作时,当 AService 的原始对象赋值(注入)给 BService,AService 会进行 AOP 操作产生一个 代理对象,这个代理对象最初会被放入单例池(一级缓存)中,也就是说此时 BService 中注入的对象是原始对象,而 AService 最终创立的实现后是代理对象,这样就会导致 BService 依赖的 AService 和 最终的 AService 不是同一个对象。
呈现这个问题次要是上文提到过的 AOP 是通过 BeanPostProcessor 实现的,而 BeanPostProcessor 是在 属性注入阶段后 才执行的,所以会导致注入的对象有可能和最终的对象不统一。

3.4 Spring 是如何通过第三级缓存来防止 AOP 问题的?
三级缓存中寄存的是 ObjectFactory 对象,那 ObjectFactory 是什么呢?

ObjectFactory 是什么?

深刻源码会发现 Spring 在 doCreateBean() 办法中的 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)) 退出缓存。

深刻 addSingletonFactory 办法:能够看到办法中的第二个参数就是 ObjectFactory 类型,并且将其增加进 三级缓存(singletonFactories)中。

这里放一下 ObjectFactory 类:

也就是说 Spring 在退出缓存时,会将 实例化后生成的原始对象 通过 lambda 表达式调用 getObject() 办法,getObject() 办法里调用 getEarlyBeanReference() 办法 来封装成 ObjectFactory 对象。

getEarlyBeanReference() 办法的作用

进入 getEarlyBeanReference() 中,会发现调用了 SmartInstantiationAwareBeanPostProcessor 的 getEarlyBeanReference() 办法。

找到 SmartInstantiationAwareBeanPostProcessor 的实现类 AbstractAutoProxyCreator 实现的 getEarlyBeanReference() 办法就能够看到其作用了。

earlyProxyReferences 存储的是 (beanName, bean) 键值对,这里的 bean 指的是原始对象(刚实例化后的对象)。
wrapIfNecessary() 办法用于执行 AOP 操作,生成一个代理对象(也就是说如果有 AOP 操作最初返回的是代理对象,否则返回的还是原始对象)。

Spring 真正意义上地创立 Bean 的流程

先放具体流程图:

要点阐明:

并不是马上就执行 ObjectFactory 的 getEarlyBeanReference() 办法(有循环依赖时才执行)。

实例化后的 Bean 会生成原始对象,而后通过 lambda 表达式封装为 ObjectFactory 对象,并且通过 addSingletonFactory() 办法将其放入 三级缓存(singletonFactories)中。
然而这里执不执行 lambda 表达式中的 getEarlyBeanReference() 办法是看程序有没有调用 singletonFactories.get(beanName),只有调用了该办法(其实也就是看是否存在循环依赖须要提前取得该 Bean),才会触发执行 getEarlyBeanReference() 办法。
而 getEarlyBeanReference() 办法会依据 Bean 中是否有 AOP 操作来决定返回的是 原始对象 还是 代理对象,并且会将其上移到二级缓存中(也就是提前裸露进去让别的 Bean 应用)。

如果 Bean 中有 AOP 操作,而 AOP 操作又是在属性注入之后执行的,那么之前的 getEarlyBeanReference() 办法中执行的 AOP 操作会不会反复?

答案是不会,还记得 getEarlyBeanReference() 办法中的 earlyProxyReferences 吗,这个就是用来记录以后 Bean 是否曾经执行 AOP 操作。
当属性注入后须要执行 AOP 操作时,会先判断以后的 Bean 是否在 earlyProxyReferences 中,如果在则阐明曾经提前执行了 AOP 了,不必再执行了,否则就执行以后 AOP 操作。

二级缓存中的对象什么时候会上移到一级缓存?

二级缓存是为了提前裸露 Bean 来解决循环依赖问题,此时的 Bean 可能还没有进行属性注入,只有等实现了属性注入、初始化后的 Bean 才会上移到一级缓存(单例池)中。

为什么能够解决 AOP 的问题?

三级缓存通过利用 ObjectFactory 和 getEarlyBeanReference() 做到了提前执行 AOP 操作从而生成代理对象。
这样在上移到二级缓存时,能够做到如果 Bean 中有 AOP 操作,那么提前裸露的对象会是 AOP 操作后返回的代理对象;如果没有 AOP 操作,那么提前裸露的对象会是原始对象。
这样就能做到呈现循环依赖问题时,注入依赖的对象和最终生成的对象是同一个对象。(相当于 AOP 提前在属性注入前实现,这样就不会导致前面生成的代理对象与属性注入时的对象的不统一)

所以 Spring 利用 三级缓存 奇妙地将呈现 循环依赖 时的 AOP 操作 提前到了 属性注入 之前,防止了对象不统一问题。

  1. 梳理 Spring 解决 Bean 的循环依赖的整个流程
    还是以 AService 和 BService 的循环依赖为例,残缺地看看 Spring 是如何解决 Bean 的循环依赖问题。

源码剖析整个流程

因为后面的内容过于繁琐,这里就以文字概括,只关注几个次要的办法:
以 AbstractApplicationContext 的 refresh() 办法登程,进入 finishBeanFactoryInitialization() 办法再进入 preInstantiateSingletons() 办法再进入 getBean() 办法再进入 doGetBean() 办法。
看看 doGetBean() 办法:

其中的第一个 getSingleton(beanName) 是判断 三级缓存 中是否有创立好的 Bean 对象,看看源码:

能够看到这里别离去每一级的缓存中取数据,顺次从第一级开始取数据,如果获得到则间接返回,取不到则往下一级查找。
能够看到在第三级缓存中调用了 singletonFactories.get(beanName) 依照上文所说的会触发执行有 AOP 操作返回代理对象,没有返回原始对象,并且在这里会判断取出的数据是否存在,存在则上移到二级缓存中并删除三级缓存的数据。
如果都没有的话就会执行第二个 getSingleton() 也就是去执行 createBean() 创立一个 Bean 对象进去。
会执行 createBean() 办法中的 doCreateBean() 办法,看看源码:

到这里应该就高深莫测了。

梳理整个流程

首先会获取 AService 对应的 Bean 对象。
先是调用 doGetBean() 中的第一个 getSingleton(beanName) 判断是否有该 Bean 的实例,有就间接返回了。(显然这里没有)
而后调用 doGetBean() 中的第二个 getSingleton() 办法来执行 doCreateBean() 办法。
先进行实例化操作(也就是利用构造函数实例化),此时实例化后生成的是原始对象。
将原始对象通过 lambda 表达式 进行封装成 ObjectFactory 对象,通过 addSingletonFactory 退出三级缓存中。
而后再进行属性注入,此时发现须要注入 BService 的 Bean,会通过 doGetBean() 去获取 BService 对应的 Bean。
同样调用 doGetBean() 中的第一个 getSingleton(beanName) 判断是否有该 Bean 的实例,显然这里也是不会有 BService 的 Bean 的。
而后只能调用 doGetBean() 中的第二个 getSingleton() 办法来执行 doCreateBean() 办法来创立一个 BService 的 Bean。
同样地先进行实例化操作,生成原始对象后封装成 ObjectFactory 对象放入三级缓存中。
而后进行属性注入,此时发现须要注入 AService 的 Bean,此时调用调用 doGetBean() 中的第一个 getSingleton(beanName) 查找是否有 AService 的 Bean。此时会触发三级缓存,也就是调用 singletonFactories.get(beanName)。
因为三级缓存中有 AService 的原始对象封装的 ObjectFactory 对象,所以能够获取到的代理对象或原始对象,并且上移到二级缓存中,提前裸露给 BService 调用。
所以 BService 能够实现属性注入,而后进行初始化后,将 Bean 放入一级缓存,这样 AService 也能够实现创立。

以上就是 Spring 解决 Bean 的循环依赖问题的整个流程了。

正文完
 0