首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

Spring 循环依赖

  • 25-03-05 15:40
  • 4405
  • 5052
blog.csdn.net

优质博文:IT-BLOG-CN

一、问题现状

在Spring框架中,循环依赖Circular Dependency是指两个或多个Bean相互依赖,形成一个循环引用。例如,Bean A依赖于Bean B,而Bean B又依赖于Bean A。这种情况可能会导致Spring容器在创建Bean时出现问题。如下:

public class A {
    private final B b;

    public A(B b) {
        this.b = b;
    }
}

public class B {
    private final A a;

    public B(A a) {
        this.a = a;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

启动时会出现如下错误:

org.springframework.beans.factory.BeanCurrentlyInCreationException: 
Error creating bean with name 'A': Requested bean is currently in creation: Is there an unresolvable circular reference?
  • 1
  • 2

二、解决循环依赖的方法

【1】构造器注入: 构造器注入不支持循环依赖,因为在Spring容器尝试创建Bean时,它需要立即解析所有的构造函数参数,这会导致循环依赖问题。因此,避免使用构造器注入来解决循环依赖。@Lazy注解可以延迟Bean的初始化,使得Spring在构造器注入时也能解决循环依赖问题。具体来说,@Lazy会告诉Spring在第一次使用Bean时才进行初始化,而不是在容器启动时立即初始化。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

@Component
public class A {
    private final B b;

    @Autowired
    public A(@Lazy B b) {
        this.b = b;
    }
}

@Component
public class B {
    private final A a;

    @Autowired
    public B(@Lazy A a) {
        this.a = a;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

【2】Setter注入: Setter注入可以解决循环依赖问题,因为Spring容器可以首先创建Bean的实例,然后再注入依赖。

public class A {
    private B b;

    public void setB(B b) {
        this.b = b;
    }
}

public class B {
    private A a;

    public void setA(A a) {
        this.a = a;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

【3】@Autowired注解: 使用@Autowired注解进行Setter注入或者字段注入,也可以解决循环依赖问题。

public class A {
    @Autowired
    private B b;
}

public class B {
    @Autowired
    private A a;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

【4】@Lazy注解: 使用@Lazy注解可以延迟Bean的初始化,从而解决循环依赖问题。

public class A {
    @Autowired
    @Lazy
    private B b;
}

public class B {
    @Autowired
    @Lazy
    private A a;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

【5】使用ObjectFactory或Provider: 使用ObjectFactory或Provider可以在需要时才获取Bean实例,从而解决循环依赖问题。

public class A {
    @Autowired
    private ObjectFactory<B> bFactory;

    public void someMethod() {
        B b = bFactory.getObject();
        // 使用B
    }
}

public class B {
    @Autowired
    private ObjectFactory<A> aFactory;

    public void someMethod() {
        A a = aFactory.getObject();
        // 使用A
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

【6】配置allow-circular-references: true: 用于控制是否允许Bean之间的循环依赖。true:允许Bean之间存在循环依赖。Spring容器会尝试通过创建Bean的代理对象来解决循环依赖问题。这是默认行为。但从设计和架构的角度来看,尽量避免循环依赖是更好的做法。

spring:
  main:
    allow-circular-references: true
  • 1
  • 2
  • 3

三、三级缓存的组成

首先,我们要知道Spring在创建Bean的时候默认是按照自然排序进行创建的,所以第一步Spring会去创建A。

Spring创建Bean的过程中分为三步:
1、实例化: 对应方法AbstractAutowireCapableBeanFactory中的createBeanInstance方法,简单理解就是new了一个对象。
2、属性注入: 对应方法AbstractAutowireCapableBeanFactory的populateBean方法,为实例化中new出来的对象填充属性。
3、初始化: 对应方法AbstractAutowireCapableBeanFactory的initializeBean,执行aware接口中的方法,初始化方法,完成AOP代理。

Spring是如何解决循环依赖问题的:三级缓存

三级缓存的组成
一级缓存singletonObjects: 存储已经完全初始化的单例Bean。类型ConcurrentHashMap
二级缓存earlySingletonObjects: 多了一个early,表示缓存的是早期的bean对象。早期是什么意思?表示Bean的生命周期还没走完就把这个Bean放入earlySingletonObjects,通常是为了避免循环依赖。类型ConcurrentHashMap
三级缓存singletonFactories: 存储创建Bean的工厂ObjectFactory,用于解决循环依赖。类型:ConcurrentHashMap>

/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);

/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);

/** Cache of early singleton objects: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

四、三级缓存的工作原理

创建Bean实例: 当Spring容器创建一个Bean时,首先会尝试从一级缓存singletonObjects中获取该Bean。如果获取不到,再尝试从二级缓存earlySingletonObjects中获取。如果仍然获取不到,再尝试从三级缓存singletonFactories中获取。

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

提前曝光Bean: 在创建Bean的过程中,如果检测到循环依赖,Spring会提前将该Bean的一个早期引用(通常是通过ObjectFactory创建的代理对象)放入三级缓存singletonFactories中。

解决循环依赖: 当另一个Bean需要依赖这个尚未完全初始化的Bean时,会从三级缓存singletonFactories中获取该Bean的早期引用,并将其放入二级缓存earlySingletonObjects中。

完成初始化: 一旦Bean完全初始化完成,Spring会将其从二级缓存earlySingletonObjects中移除,并放入一级缓存singletonObjects中。

案例:如上有两个类A和B,它们通过构造器注入互相依赖:
【1】创建A的实例: A依赖B,但B尚未创建。Spring会将A的早期引用(通常是一个代理对象)放入三级缓存singletonFactories中。
【2】创建B的实例: B依赖A,Spring会从三级缓存singletonFactories中获取A的早期引用,并将其放入二级缓存 earlySingletonObjects中。通过早期引用可知,B注入的是A的引用,所以最终拿到的是一个完整的A对象。
【3】完成B的初始化: B完全初始化后,放入一级缓存singletonObjects中。
【4】完成A的初始化: A获取到B的完全初始化的实例后,完成自身初始化,并放入一级缓存singletonObjects中。

五、spring 循环依赖为什么使用三级缓存而不是二级缓存

【1】代理对象的创建: 在某些情况下,Spring需要为bean创建代理对象(例如,使用AOP时)。代理对象的创建通常在bean初始化的后期阶段进行。

如果只使用二级缓存,意味着所有Bean在实例化后就要完成AOP代理,在某些情况下,Spring可能无法正确地创建代理对象,因为代理对象的创建依赖于完整的bean初始化过程,这样违背了Spring设计的原则,Spring在设计之初就是通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来在Bean生命周期的最后一步来完成AOP代理,而不是在实例化后就立马进行AOP代理。

三级缓存中的对象工厂可以确保在需要时创建代理对象,并将其放入二级缓存,从而确保代理对象可以在循环依赖中正确地被引用。

【2】延迟创建早期引用: 三级缓存中的对象工厂允许Spring在需要时延迟创建早期引用,而不是立即创建。这种延迟创建机制可以确保在某些特殊情况下,bean可以在完全初始化之前被引用。

通过这种方式,Spring可以更灵活地处理各种复杂的依赖关系和代理对象的创建。

文章知识点与官方知识档案匹配,可进一步学习相关知识
Java技能树使用JDBC操作数据库数据库操作152423 人正在系统学习中
Java程序员进阶
微信公众号
普通程序员如何演变为架构师和管理者
注:本文转载自blog.csdn.net的程序猿进阶的文章"https://blog.csdn.net/zhengzhaoyang122/article/details/142691283"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2491) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

103
后端
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top