Spring循环依赖

Spring-bean的循环依赖以及解决方式

Spring里面Bean的生命周期和循环依赖问题

什么是循环依赖?

循环依赖其实就是循环引用,也就是两个或者两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A。如下图:

注意,这里不是函数的循环调用,是对象的相互依赖关系。循环调用其实就是一个死循环,除非有终结条件。

Spring中循环依赖场景有:

(1)构造器的循环依赖

(2)field属性的循环依赖

其中,构造器的循环依赖问题无法解决,只能拋出BeanCurrentlyInCreationException异常,在解决属性循环依赖时,spring采用的是提前暴露对象的方法。

怎么检测是否存在循环依赖

检测循环依赖相对比较容易,Bean在创建的时候可以给该Bean打标,如果递归调用回来发现正在创建中的话,即说明了循环依赖了。

Spring怎么解决循环依赖

Spring的循环依赖的理论依据基于Java的引用传递,当获得对象的引用时,对象的属性是可以延后设置的。(但是构造器必须是在获取引用之前)。

完整实例bean,需要通过上面三个步骤:

1.  createBeanInstance方法: 得到一个实例bean,但是没有进行属性值的注入

2.  populateBean方法:就是对bean进行属性值注入。

3.  initializeBean方法:如果配置文件里面有init方法,需要执行init方法。

可以看出第一步和第二步比较重要,这里面就是解决bean依赖的关键。

我们可以看出进行createBeanInstance方法,得到了bean实例对象,但是没有属性注入。把没有完全实例化的bean,放到addSinletonFactory方法里面去,这样相当于就是提前暴露bean,接下来addSinletonFactory方法,这里面使用三个Map类型的,及三级缓存来解决循环依赖。接下我们看一下addSingletonFactory方法实现。

singleObjects--》单例对象的cache:一级缓存

earlySingletonObjects --》提前暴光的单例对象的Cache :二级缓存

singletonFactories --》单例对象工厂的cache :三级缓存

首先解释两个参数:

  • isSingletonCurrentlyInCreation 判断对应的单例对象是否在创建中,当单例对象没有被初始化完全(例如A定义的构造函数依赖了B对象,得先去创建B对象,或者在populatebean过程中依赖了B对象,得先去创建B对象,此时A处于创建中)
  • allowEarlyReference 是否允许从singletonFactories中通过getObject拿到对象

在创建bean的时候,首先想到的是从cache中获取这个单例的bean,这个缓存就是singletonObjects。如果获取不到,并且对象正在创建中,就再从二级缓存earlySingletonObjects中获取。如果还是获取不到且允许singletonFactories通过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取,如果获取到了则:从singletonFactories中移除,并放入earlySingletonObjects中。其实也就是从三级缓存移动到了二级缓存。

Spring解决循环依赖的诀窍就在于singletonFactories这个cache,这个cache中存的是类型为ObjectFactory,其定义如下

public interface ObjectFactory<T> {
    T getObject() throws BeansException;}

在bean创建过程中,有两处比较重要的匿名内部类实现了该接口。一处是

new ObjectFactory<Object>() {
   @Override   public Object getObject() throws BeansException {
      try {
         return createBean(beanName, mbd, args);
      }      catch (BeansException ex) {
         destroySingleton(beanName);
         throw ex;
      }   }

另一处就是

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

这里就是解决循环依赖的关键,这段代码发生在createBeanInstance之后,也就是说单例对象此时已经被创建出来(调用了构造器)。这个对象已经被生产出来了,虽然还不完美(还没有进行初始化的第二步和第三步),但是已经能被人认出来了(根据对象引用能定位到堆中的对象),所以Spring此时将这个对象提前曝光出来让大家认识,让大家使用。

这样做有什么好处呢?让我们来分析一下“A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象”这种循环依赖的情况。A首先完成了初始化的第一步,并且将自己提前曝光到singletonFactories中,此时进行初始化的第二步,发现自己依赖对象B,此时就尝试去get(B),发现B还没有被create,所以走create流程,B在初始化第一步的时候发现自己依赖了对象A,于是尝试get(A),尝试一级缓存singletonObjects(肯定没有,因为A还没初始化完全),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories,由于A通过ObjectFactory将自己提前曝光了,所以B能够通过ObjectFactory.getObject拿到A对象(虽然A还没有初始化完全,但是总比没有好呀),B拿到A对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects中。此时返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化,进去了一级缓存singletonObjects中,而且更加幸运的是,由于B拿到了A的对象引用,所以B现在hold住的A对象完成了初始化。

知道了这个原理时候,肯定就知道为啥Spring不能解决“A的构造方法中依赖了B的实例对象,同时B的构造方法中依赖了A的实例对象”这类问题了!因为加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决。

为啥要三级缓存

必须使用三级缓存吗 两级不行吗 提前暴露时直接放到二级缓存earlySingletonObjects里会有什么问题呢?

answer:如果不存在循环依赖,那么就没必要提早曝光放到earlySingletonObjects里。如果像你说的只有两级缓存,那简单的A依赖B,A在第一步初始化未注入field的时候就放到了earlySingletonObjects,显然不符合常理,只能说放到三级缓存中预防循环依赖的产生。singletonFactories 并没有提早曝光的意思,只是为了缓存,earlySingletonObjects才为了提早曝光,所以才要三级的。

原文地址:https://www.cnblogs.com/fanguangdexiaoyuer/p/10693319.html

时间: 2024-08-28 08:48:37

Spring循环依赖的相关文章

spring循环依赖问题分析——待后续补充

新搞了一个单点登录的项目,用的cas,要把源码的cas-webapp改造成适合我们业务场景的项目,于是新加了一些spring的配置文件. 但是在项目启动时报错了,错误日志如下: 一月 08, 2016 11:01:34 下午 org.apache.catalina.core.StandardContext filterStart 严重: Exception starting filter springSecurityFilterChain org.springframework.beans.fa

SPRING循环依赖(circular reference)的解决方法

循环依赖,就是说类A依赖与B,而B又依赖于A,这种情况本不应该发生,发生这种情况是因为我在项目中使用的工厂模式,用一个工厂来生产一些管理器类,而某一管理器要需要另一管理器提供支持所以就要引用工厂类,而这个管理器和这个工厂就出现了循环依赖(项目中实际的逻辑比这个更复杂,因为我在项目中实现的了一个工作流数据POJO类延迟加载的功能像hibernate 那样在调用一个类的集合属性时才到要shark中去查找数据而不是在new里加载,并且这个数据类的集合属性并不包含加载数据的代码只是普通的Bean方法ge

面试中被问Spring循环依赖的三种方式!!!

什么是循环依赖? 循环依赖其实就是循环引用,也就是两个或则两个以上的 Bean 互相持有对方,最终形成闭环.比如A依赖于B,B依赖于C,C又依赖于A.如下图: 如果在日常开发中我们用new 对象的方式发生这种循环依赖的话程序会在运行时一直循环调用,直至内存溢出报错.下面说一下Spring是如果解决循环依赖的. 注意,这里不是函数的循环调用,是对象的相互依赖关系.循环调用其实就是一个死循环,除非有终结条件. 根据 spring 中 Bean 的注入方式:构造器注入方式,属性注入方式(单例和多例),

spring循环依赖问题深究

一.背景 清分服务添加一个异步处理功能(@asyc),本地测试时发现启动服务后有时正常有时异常. 问题分析 1.相同的环境,启动服务结果不同,定位为工程代码引起该异常. 2.启动类中,配置文件名采用了通配符,理论上配置文件加载顺序不固定. 3.从机器上的日志可以看出,启动成功和异常时spring/application-xxx.xml加载顺序不同. 4.从错误日志可以看出两个问题点:循环依赖和不同的bean依赖了同一个bean的不同版本.(spring默认支持循环依赖) 说明: 1.bean的实

spring循环依赖是怎么解决的?

回答:循环依赖就是循环引用,就是两个或多个Bean相互之间的持有对方,比如CircleA引用CircleB,CircleB引用CircleA,则它们最终反映为一个环. Spring如何解决循环依赖? 假设场景如下,A->B->A 1.实例化A,并将未注入属性的A暴露出去,即提前曝光给容器Wrap2.开始为A注入属性,发现需要B,调用getBean(B)3.实例化B,并注入属性,发现需要A的时候,从单例缓存中查找,没找到时继而从Wrap中查找,从而完成属性的注入4.递归完毕之后回到A的实例化过程

Spring循环依赖原因及如何解决

浅谈Spring解决循环依赖的三种方式 SpringBoot构造器注入循环依赖及解决 原文:https://www.baeldung.com/circular-dependencies-in-spring 代码:https://github.com/eugenp/tutorials/tree/master/spring-di 原文地址:https://www.cnblogs.com/liran123/p/11655604.html

01 spring循环依赖

作者:Mythsman原文:https://blog.mythsman.com/post/5d838c7c2db8a452e9b7082c/转载自:猿天地原文来自:https://mp.weixin.qq.com/s/pIawXWHCGqIHi0INHDgawQ 1 什么是依赖 其实,不分场景地.笼统地说A依赖B其实是不够准确.至少是不够细致的.我们可以简单定义一下什么是依赖. 所谓A依赖B,可以理解为A中某些功能的实现是需要调用B中的其他功能配合实现的.这里也可以拆分为两层含义: A强依赖B.

Spring中-IOC-Bean的初始化-循环依赖的解决

前言 在实际工作中,经常由于设计不佳或者各种因素,导致类之间相互依赖.这些类可能单独使用时不会出问题,但是在使用Spring进行管理的时候可能就会抛出BeanCurrentlyInCreationException等异常 .当抛出这种异常时表示Spring解决不了该循环依赖,本文将简要说明Spring对于循环依赖的解决方法. 循环依赖的产生和解决的前提 循环依赖的产生可能有很多种情况,例如: A的构造方法中依赖了B的实例对象,同时B的构造方法中依赖了A的实例对象 A的构造方法中依赖了B的实例对象

Spring之循环依赖

转:http://my.oschina.net/tryUcatchUfinallyU/blog/287936 概述 如何检测循环依赖 循环依赖如何解决 Spring如何解决循环依赖 主要的几个缓存 主要步骤 Spring不能完全解决的循环依赖问题 面对Spring不能完全解决的现状,我们该如何处理 概述 入职的时候学习spring研究过循环依赖,现在再回顾下,发现啥都忘记了,还是得总结下来,故总结该文. 本文主要解决如下问题: 1.何为循环依赖 2.如何检测循环依赖 3.循环依赖可以如何解决 4