Java Spring处理循环依赖详解
目录
- 01-前言:什么是循环依赖?
- 02-Spring 如何处理循环依赖?
- 03-Spring 中解决循环依赖的三级缓存
01-前言:什么是循环依赖?
首先,我们先明确下依赖的定义。 如果一个 Bean bar 的属性,引用了容器中的另外一个 Bean foo,那么称 foo 为 bar 的依赖,或称 bar 依赖 foo。 如果用代码表示,可以表示为:
@Component("foo") public Class Foo { @Autowired private Bar bar; // 称 foo 依赖 bar } @Component("bar") public Class Bar { @Autowired private Baz baz; // bar 依赖 baz }
其次,循环引用的概念是指多个 Bean 之间的依赖关系形成了环。 接着上面的例子,如果 baz 依赖 foo 或 bar 都将形成循环依赖。
@Component("baz") public class Baz { @Autowired private Foo foo; // 形成了循环依赖,baz -> (依赖) foo -> bar -> baz ... }
02-Spring 如何处理循环依赖?
在之前的文章中,我跟大家一块学习了 Spring 创建 Bean 过程的源码。 我们知道:在 createBeanInstance 阶段,需要解决构造器、工厂方法参数的依赖; 在 populateBean 阶段,需要解决类属性中对其他 Bean 的依赖。 这其实对应了 Spring 中支持的两种依赖注入方式,基于构造器的依赖注入和基于 setter 方法的依赖注入,分别对应前面的两种情况。
接下来,我会通过上节介绍的示例,来分情况讨论产生循环依赖的场景。 为了使讨论过程更清楚、更简洁,我会让 foo 依赖 bar,而 bar 依赖 foo。 在接下来的描述中,我假设 Spring 会先创建 Bar 的对象,再创建 Foo 的对象。 针对不同的依赖情况,可以分为四种场景:
- 第一种场景,Bar 在构造器参数中依赖 Foo,Foo 在构造器参数中依赖 Bar。这种场景下,依赖的注入发生在 Bar 和 Foo 的实例化阶段。
- 第二种场景,Bar 在构造器参数中依赖 Foo,Foo 通过 setter 函数依赖 Bar。这种场景下,Bar 中注入 Foo 发生在实例化阶段,Foo 中注入 Bar 发生在属性填充阶段。
- 第三种场景,Bar 通过 setter 函数依赖 Foo,Foo 在构造器参数中依赖 Bar。这种场景下,Bar 中输入 Foo 发生在属性填充阶段,而 Foo 中注入 Bar 发生在实例化阶段。
- 第四种场景,Bar 通过 setter 函数依赖 Foo,Foo 通过 setter 函数依赖 Bar。 这种场景下,依赖注入均发生在属性填充阶段。
在具体分析上述四种场景之前,先说下结论: Spring 可以解决场景三、四中出现循环依赖的情况,而第一、二种场景,Spring 无法解决,需要重构依赖或者延迟延迟依赖注入的时机(例如使用 @Lazy 等)。 细心的读者可能会问第二种、第三种场景有什么不同呢? 其实第二、第三种场景本质上是同一种情况,唯一的不同是实例化的先后顺序。 结合这个信息,可以得出,先创建的类以构造器参数方式依赖其他 Bean,则会发生循环依赖异常。 反过来,如果先创建的类以 setter 方式依赖其他 Bean,则不会发生循环依赖异常。
接下来,我会详细分析每一种场景,并指出抛循环依赖异常的时机。
首先,所有的单例 Bean 会在容器启动后被创建 ConfigurableListableBeanFactory#preInstantiateSingletons,即所谓的 “eager registration of singletons” 过程。
第一种场景,会先触发 Bar 类的实例 bar 的创建。在 createBeanInstance 阶段,会通过 ConstructorResolver#autowireConstructor 来创建实例。 ConstructorResolver#createArgumentArray 会解析构造器中的参数,并处理对其他 Bean 依赖的引用 ConstructorResolver#resolveAutowiredArgument。 处理依赖的方式就是通过 DefaultListableBeanFactory#resolveDependency 来查找符合条件的 Bean,最终还是通过 AbstractBeanFactory#getBean 来从容器中取。 当通过 getBean("bar") 来触发 Spring 创建 bar 时,在实例化阶段,根据构造器参数来 getBean("foo") 并触发 foo 的创建。 在 foo 的实例化过程与 bar 的是完全一样的,最终 getBean("bar")。这是容器中的 bar 还没有创建好,所以会再次触发创建过程。 在真正创建过程之前,在 DefaultSingletonBeanRegistry#getSingleton 中会有一次检查,DefaultSingletonBeanRegistry#beforeSingletonCreation 如果发现要创建的 bean 正在创建过程中,则抛出异常。
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'bar': Requested bean is currently in creation: Is there an unresolvable circular reference?
第二种场景,同样先构建 bar 实例。与第一种场景不同之处在于 foo 创建时,它的 createBeanInstance 阶段能够执行完毕。 原因是 foo 只有一个无参构造器(即默认构造器),不需要注入其他依赖。 foo 的 createBeanInstance 阶段执行完毕后,会进入 populateBean 阶段。 在这个阶段中,AutowiredAnnotationBeanPostProcessor#postProcessProperties 会处理 setter 函数依赖的 Bean。 大致处理过程为:AutowiredAnnotationBeanPostProcessor 识别到 foo 中包含需要注入依赖的 setter 函数,将其映射为 AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement 对象。 然后调用 AutowiredMethodElement#inject 方法注入依赖。 在 inject 方法中,会调用 DefaultListableBeanFactory#resolveDependency 来查找对应的依赖。 到这里为止,后续的过程与第一种场景完全一致了。 从容器中尝试获取 bar,发现不存在,会出发 bar 的再次创建,最终在 DefaultSingletonBeanRegistry#beforeSingletonCreation 中抛出异常。
第三种场景,同样先构建 bar 实例。由于它只包含一个默认构造器,所以它的 createBeanInstance 阶段会顺利完成,然后进入 populateBean 阶段。 当你仔细回看一下 Spring 创建 Bean 过程的源码,你会发现下面这段代码:
if (earlySingletonExposure) { addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); }
这段逻辑发生在 createBeanInstance 之后,尚未进入 populateBean 之前。 这里其实就是 Spring 解决循环依赖机制的核心点之一,这里我暂且不深入介绍,后面会有详细的分析。
继续前面的分析。bar 实例创建进入到 populateBean 阶段后,会检查其自身依赖情况,然后注入对应的依赖 Bean。 这里的处理逻辑依然是 AutowiredAnnotationBeanPostProcessor#postProcessProperties 来处理。 当尝试注入 foo 时,会出发 foo 实例的创建过程。foo 通过构造器依赖 bar,因此在其 createBeanInstance 阶段,会通过编程客栈 ConstructorResolver#autowireConstructor 完成依赖注入。 此时通过 getBean("bar") 从容器中尝试获取 bar 时,能够“获取到”。 注:这里为什么不会出发 bar 的创建,反而能够直接得到 bar 对象呢?上面的获取到我加了引号,它其实获得的并不是一个完整、可用的 bar。 它获得的是通过 earlySingletonExposure 提前暴露出的对象。 这个过程在后面介绍三级缓存时会详细介绍。
篇幅原因,第四种场景我不在这里继续分析,感兴趣的读者可以自己尝试分析下。 简单提示下,它的过程有点像第三种场景前半段、第二种场景的后半段结合起来。
在上述四种场景下,第一种情况,依赖双方都是通过构造器依赖对方,这种情况下 Spring 是无法处理的。 而且,我认为出现这一情况,属于是设计上的缺陷,应当通过重新设计依赖关系来解决,例如可以将基于构造器的注入修改为基于 setter 的注入,或者通过 @Lazy 将依赖的初始化延迟到使用时。 通过 Foo、Bar 类来举例说明。
@Component public class Foo { private Bar bar; @Autowired public Foo(@Lazy Bar bar) { this.bar = bar; } } @Component public class Bar { private Foo foo; @Autowired public Bar(Foo foo) { // 或者将对 foo 的依赖,注解为 @Lazy 表示使用时才初始化 this.foo = foo; } }
另一种修改方式就是,将第一种情况,修改为第二种情况,即:
@Component public class Bar { private Foo foo; @Autowired public void setFoo(Foo foo) { this.foo = foo; } }
03-Spring 中解决循环依赖的三级缓存
Spring 中设计了一个三级缓存用来解决前面介绍的循环依赖问题的处理。三级缓存包括:
- singletonObjects,为一级缓存,保存了 beanName -> bean instance 的映射关系。存放的是完全可用的单例 Bean 对象。
- earlySingletonObjects,为二级缓存,保存了 beanName -> bean instance 的映射关系。 在一级、二级缓存都没有发现目标对象,但三级缓存中存在 ObjectFactory 对象时,调用 ObjectFactory#getObject 创建实例,放入二级缓存,删除三级缓存中的 ObjectFactory 对象。
- singletonFactories,为三级缓存,保存了 beanName -> ObjectFactory 的映射关系。 在 doCreateBean 时,会向这个 map 中添加
beanName: () -> getEarlyBeanReference(beanName, mbd, bean)
的映射关系,value 是一个函数式接口 ObjectFactory。
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { synchronized (this.singletonObjects) { if (!this.singletonObjects.containsKey(beanName)) { this.singletonFactories.put(beanNameandroid, singletonFactory); this.earlySingletonObjects.remophpve(beanName); this.registeredSingletons.add(beanName); } } }
大概了解了 Spring 中的三级缓存后,我们再回过头来看一下 AbstractBeanFactory#getBean 过程。 它的实际工作是在 AbstractBeanFactory#doGetBean 中完成的。 doGetBean 方法的具体实现可以简化、抽象为:
protected <T> T doGetBean( String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException { Object sharedInstance = getSingleton(beanName); if (sharedInstance != null && args == null) { // 缓存中存在 /** * 如果 beanName 是一个 FactoryBean,则获取对应的 Bean * 如果 beanName 是一个普通的 Bean,则返回这个 Bean 本身 */ beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null); } else { // 缓存中不存在 // 创建对象 if (mbd.isSingleton()) { /** * 这里的 getSingleton 是 getSingleton(beanName) 的重载版本 * 它接受一个 beanName 和 一个 ObjectFactory 作为参数 * 调用 ObjectFactory#getObject 产生一个实例 * 并通过 addSingleton(beanName, singletonObject); 将实例添加到 singletonObjects 中 * 这里 createBean 的代码就是前面提到的 Spring 创建 Bean 实例的过程 doCreateBean */ sharedInstance = getSingleton(beanName,http://www.devze.com () -> { try { return createBean(beanName, mbd, args); } }); /** 编程客栈* 同前面分支中的作用一样 */ beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } } /** * 判断 Bean 类型与 requiredType 类型是否一直,一致则直接返回,不一致则需要进行转换 */ return adaptBeanInstance(name, beanInstance, requiredType); }
从上面的源码可以知道,当 DefaultSingletonBeanRegistry#getSingleton(beanName) 时,会先从多级缓存中取对象(可能是 bean instance,也可能是对应的 ObjectFactory)。 从多级缓存中取对象的源码如下:
protected Object getSingleton(String beanName, boolean allowEarlyReference) { // Quick check for existing instance without full singleton lock Object singletonObject = this.singletonObjects.get(beanName); /** * 判断第一级缓存中是否有完全可以可用的 Bean 实例,若有则返回; * 若没有,则根据情况判断 * isSingletonCurrentlyInCreation(beanName) 检查的是在 `Set<String> singletonsCurrentlyInCreation` 集合中是否包含要获取的 Bean 实例 * beanName 只在调用 beforeSingletonCreation(String beanName) 时被添加到 singletonsCurrentlyInCreation 集合中 * beforeSingletonCreation 在创建 bean,即 doCreateBean 之前调用,在创建过程完成以后,调用 afterSingletonCreation 从集合中移除 beanName */ if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { /** * 执行到这个分支,其实说明 Bean 已经在创建过程中了,只不过是尚未完全可用(即一级缓存中没有) * 检查二级缓存,是否包含指定的 Bean * * 二级缓存里的内容何时被添加或设置进来的呢? * 我们可以检查下 earlySingletonObjects.put 方法都在哪里调用。 * 检查后发现,其实 earlySingletonObjects 就是在当前方法中设置的,我们接着往下看。 */ singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { /** * 这里的 allowEarlyReference 的意思就是指是否允许在二级缓存中创建一个对象,即是否允许暴露未完全可用的对象 * 如果 allowEarlyReference 为假,则不会操作二级、三级缓存,而仅检查一级缓存中是否有完全可用的 Bean 实例 * 这也意味着,不允许返回未完全可用状态的 Bean * * 当发现二级缓存中没有对象,同时又允许提前引用(即 allow开发者_C培训EarlyReference 值为真) * 则检查三级缓存中是否有对应的 ObjectFactory 对象,若有,则调用它的 getObject 方法产生对象,然后将其放置到二级缓存中,同时删除三级缓存中的对象工厂实例 * 若三级缓存中也没有对象工厂实例,则说面 bean 还未创建 */ synchronized (this.singletonObjects) { /** * 这里会进行一个 double-check,避免多线程间的线程安全问题 */ // Consistent creation of early reference within full singleton lock 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; }
注:
- isSingletonCurrentlyInCreation(beanName) 意味着什么呢?意味着对应的 Bean 在 doCreateBean 过程中,可能在 createBeanInstance \ populateBean \ initializeBean 阶段中。
- 在前面提到,createBeanInstance 后,Bean 会被添加到上述多级缓存中的第三级缓存中,存入对象是 beanName -> objectFactory 映射关系。 当其他的 Bean 依赖当前 Bean 时,而且允许引用提前暴露的 Bean(即未完全可用的 Bean),会检查第二级缓存,如果没有还会检查第三级缓存,并在得到对应 objectFactory 时,获得对象并将其从第三级移动到第二级。
有些读者看到这里可能会有个疑问,那二级缓存中的对象什么时候删除呢? 我们再来回头看下 doGetBean 中的代码片段:
sharedInstance = getSingleton(beanName, () -> { try { return createBean(beanName, mbd, args); } });
这里的 singleton
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { synchronized (this.singletonObjects) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { /** * 检查 Bean 是否在创建过程中,避免重复创建, * 不能解决的循环依赖也是在这里抛出异常 */ beforeSingletonCreation(beanName); boolean newSingleton = false; try { /** * 这里调用的其实就是 AbstractAutowireCapableBeanFactory#createBean * 然后会执行 doCreateBean(三个阶段) */ singletonObject = singletonFactory.getObject(); newSingleton = true; } catch (IllegalStateException ex) { // Has the singleton object implicitly appeared in the meantime -> // if yes, proceed with it since the exception indicates that state. singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { throw ex; } } catch (BeanCreationException ex) { throw ex; } finally { afterSingletonCreation(beanName); } if (newSingleton) { /** * 这里说明一个 bean 创建过程的三个阶段都执行完毕了 */ addSingleton(beanName, singletonObject); } } return singletonObject; } } /** * 将 Bean 实例添加到第一级缓存 * 将第二级、第三级缓存中的对象删除 */ 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); } }
到这里为止,相信你对 Spring 创建 Bean 的过程以及处理循环依赖的机制能够有个大致的了解。 如果想了解的更深或跟多细节,可以自己单步调试下。 希望今天的内容能够对你有所帮助。如果有写得不对的地方,也请大家多批评指正。
以上就是Java Spring处理循环依赖详解的详细内容,更多关于Java Spring循环依赖的资料请关注我们其它相关文章!
精彩评论