广告
返回顶部
首页 > 资讯 > 后端开发 > Python >JavaSpring处理循环依赖详解
  • 761
分享到

JavaSpring处理循环依赖详解

Java中的SpringSpring处理循环依赖 2023-05-17 05:05:01 761人浏览 八月长安

Python 官方文档:入门教程 => 点击学习

摘要

目录01-前言:什么是循环依赖?02-spring 如何处理循环依赖?03-Spring 中解决循环依赖的三级缓存01-前言:什么是循环依赖? 首先,我们先明确下依赖的定义。 如果一

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(beanName, singletonFactory);
            this.earlySingletonObjects.remove(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) {
        // 缓存中存在
         
        beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);
    } else {
        // 缓存中不存在
        // 创建对象
        
        if (mbd.isSingleton()) {
            
            sharedInstance = getSingleton(beanName, () -> {
                try {
                    return createBean(beanName, mbd, args);
                }
            });
            
            beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
        }
    }
    
    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);
    
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { 
        
        singletonObject = this.earlySingletonObjects.get(beanName);
        if (singletonObject == null && allowEarlyReference) {
            
            synchronized (this.singletonObjects) {
                
                // 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) {
                            
                            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) {
            
            beforeSingletonCreation(beanName);
            boolean newSingleton = false;
            try {
                
                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) {
                
                addSingleton(beanName, singletonObject);
            }
        }
        return singletonObject;
    }
}

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循环依赖的资料请关注编程网其它相关文章!

--结束END--

本文标题: JavaSpring处理循环依赖详解

本文链接: https://www.lsjlt.com/news/210696.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • JavaSpring处理循环依赖详解
    目录01-前言:什么是循环依赖?02-Spring 如何处理循环依赖?03-Spring 中解决循环依赖的三级缓存01-前言:什么是循环依赖? 首先,我们先明确下依赖的定义。 如果一...
    99+
    2023-05-17
    Java中的Spring Spring处理循环依赖
  • Springboot详细讲解循环依赖
    目录一、循环依赖二、循环依赖形成条件(使用构造器注入)三、循环依赖形成条件(@Aysnc注解的bean生成了对象的代理)四、针对以上问题对Spring如何解决循环依赖进行详细阐述一、...
    99+
    2022-11-13
  • 怎么理解Spring循环依赖
    本篇内容介绍了“怎么理解Spring循环依赖”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!通常来说,如果问Spring内部如何解决循环依赖,...
    99+
    2023-06-16
  • Spring循环依赖的解决方案详解
    目录简介方案1. Feild注入单例(@AutoWired)方案2. 构造器注入+@Lazy方案3. Setter/Field注入单例方案4. @PostConstruct方案5. ...
    99+
    2022-11-13
  • Java Spring 循环依赖解析
    目录1、常见问题2、什么是循环依赖?3、循环依赖说明4、BeanCurrentlyInCreationException5、依赖注入的两种方式方式一:构造器方式注入依赖方式二:以 s...
    99+
    2022-11-13
  • Spring循环依赖之问题复现详解
    目录简介问题复现1.构造器注入2.Feild注入多例(@AutoWired) 3.Setter注入多例(@AutoWired) 解决方案简介 说明 本文介绍Spr...
    99+
    2022-11-13
  • 怎么理解Spring源码循环依赖
    这篇文章主要讲解了“怎么理解Spring源码循环依赖”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“怎么理解Spring源码循环依赖”吧!Spring是怎么去...
    99+
    2022-10-19
  • 怎么理解java中Spring循环依赖
    这篇文章主要介绍“怎么理解java中Spring循环依赖”,在日常操作中,相信很多人在怎么理解java中Spring循环依赖问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”怎么理解java中Spring循环依赖...
    99+
    2023-06-16
  • Spring中怎么处理循环依赖问题
    Spring中怎么处理循环依赖问题,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。什么是循环依赖依赖指的是Bean与Bean之间的依赖关系,循环依赖指的是两个或者...
    99+
    2023-06-20
  • Java中的Spring怎么处理循环依赖
    这篇“Java中的Spring怎么处理循环依赖”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Java中的Spring怎么处理...
    99+
    2023-06-30
  • Java中的Spring 如何处理循环依赖
    目录前言什么是循环依赖构造器循环依赖Setter循环依赖构造器循环依赖处理那么Spring到底是如何做的呢?DefaultSingletonBeanRegistry#getSingl...
    99+
    2022-11-13
  • Java中的Spring循环依赖详情
    目录一、什么是循环依赖?那么循环依赖是个问题吗?二、Bean的生命周期三、三级缓存解决循环依赖思路分析四、Spring到底解决了哪种情况下的循环依赖五、总结一、什么是循环依赖? 很简...
    99+
    2022-11-13
  • Spring怎么解决循环依赖
    本篇内容介绍了“Spring怎么解决循环依赖”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!写在前面最近,在...
    99+
    2022-10-19
  • Spring轻松解决循环依赖
    目录解决循环依赖的原理源码解析总结Spring 框架是一个流行的Java应用程序框架,它提供了许多强大的功能,如依赖注入和面向切面编程。然而在使用 Spring 框架时,我们可能会遇...
    99+
    2023-05-16
    Spring循环依赖怎么解决 Spring循环依赖
  • java循环依赖如何解决
    在Java中,循环依赖是指两个或多个类之间存在相互依赖关系,导致无法编译或运行的情况。解决循环依赖的方法可以有以下几种: 重构代...
    99+
    2023-10-23
    java
  • spring循环依赖策略解析
    循环依赖所谓循环依赖就是多个Bean之间依赖关系形成一个闭环,例如A->B->C->...->A 这种情况,当然,最简单的循环依赖就是2个Bean之间互相依赖:A->B(A依赖B), B->A(B依赖A)...
    99+
    2023-05-31
    spring 循环 依赖策略
  • Spring处理@Async导致的循环依赖失败问题的方案详解
    目录简介问题复现原因分析解决方案方案1:懒加载方案2:不让@Async的类有循环依赖方案3:allowRawInjectionDespiteWrapping设置为true为什么@Tr...
    99+
    2022-11-13
  • Spring 循环依赖之AOP实现详情
    前言: 我们接着上一篇文章继续往下看,首先看一下下面的例子,前面的两个serviceA和serviceB不变,我们添加一个BeanPostProcessor: @Component ...
    99+
    2022-11-13
  • Spring循环依赖产生与解决
    目录循环依赖产生情景Spring如何解决循环依赖循环依赖产生情景 探讨如何解决循环依赖之前,更应该思考清楚什么情况下会发生这种问题? 1、模拟Prototype Bean的循环依赖 ...
    99+
    2022-12-20
    Spring如何解决循环依赖 Spring循环依赖
  • spring中如何解决循环依赖
    这期内容当中小编将会给大家带来有关spring中如何解决循环依赖,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。1.由同事抛的一个问题开始我们先看看当时出问题的代码片段:@...
    99+
    2022-10-19
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作