Spring如何解决循环依赖

本文解析了Spring框架中循环依赖的概念及处理方式,包括构造器参数循环依赖、setter方式单例和多例循环依赖的不同表现形式及其解决办法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

对自己说的话
深入spring原理对现在的自己来说确定太难了,但是要坚持,点滴的积累,一定会有收获的~加油!

什么是循环依赖
所谓的循环依赖是指,A 依赖 B,B 又依赖 A,它们之间形成了循环依赖。或者是 A 依赖 B,B 依赖 C,C 又依赖 A。它们之间的依赖关系如下:

根据创建对象的方式不同, 分为三种情况:

第一种:构造器参数循环依赖
第二种:setter方式单例,默认方式
第三种:setter方式原型,prototype,也就是多例
第一种:构造器参数循环依赖
Spring容器会将每一个正在创建的Bean 标识符放在一个“当前创建Bean池”中,Bean标识符在创建过程中将一直保持
在这个池中,因此如果在创建Bean过程中发现自己已经在“当前创建Bean池”里时将抛出
BeanCurrentlyInCreationException异常表示循环依赖;而对于创建完毕的Bean将从“当前创建Bean池”中清除掉。

首先我们先初始化三个Bean。

public class StudentA {
 
    private StudentB studentB ;
 
    public void setStudentB(StudentB studentB) {
        this.studentB = studentB;
    }
 
    public StudentA() {
    }
    
    public StudentA(StudentB studentB) {
        this.studentB = studentB;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class StudentB {
 
    private StudentC studentC ;
 
    public void setStudentC(StudentC studentC) {
        this.studentC = studentC;
    }
    
    public StudentB() {
    }
 
    public StudentB(StudentC studentC) {
        this.studentC = studentC;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class StudentC {
 
    private StudentA studentA ;
 
    public void setStudentA(StudentA studentA) {
        this.studentA = studentA;
    }
 
    public StudentC() {
    }
 
    public StudentC(StudentA studentA) {
        this.studentA = studentA;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
上面是很基本的3个类,,StudentA有参构造是StudentB。StudentB的有参构造是StudentC,StudentC的有参构造是StudentA ,这样就产生了一个循环依赖的情况,
我们都把这三个Bean交给Spring管理,并用 有参构造实例化对象到容器中

     
        
    
    
        
    
    
        
     
1
2
3
4
5
6
7
8
9
下面的测试类:

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("com/zuoyueer/applicationContext.xml");
        System.out.println(context.getBean("a", StudentA.class));
    }
}
1
2
3
4
5
6
执行结果报错信息为:

Caused by: 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
分析原因:
Spring容器创建"StudentA " bean,首先去"当前创建bean池"查找是否当前bean正在创建,如果没有发现,则继续准备其需要的构造器参数"StudentB",并将"StudentA “标识符放置到"当前创建bean池”.

Spring容器创建"StudentB" bean,首先去"当前创建bean池"查找是否当前bean正在创建,如果没有发现,则继续准备其需要的构造器参数"StudentC",并将"StudentB"标识符放置到"当前创建bean池".

Spring容器创建"StudentC" bean,首先去"当前创建bean池"查找是否当前bean正在创建,如果没有发现,则继续准备其需要的构造器参数"StudentA “,并将"StudentC"标识符放置到"当前创建bean池”.

到此为止Spring容器要再次去创建"StudentA " bean,发现该bean标识符在"当前创建bean池"中,因为表示循环依赖,故抛出BeanCurrentlyInCreationExpection异常。因为在池中的Bean都是未初始化完的,所以会依赖错误 ,(初始化完的Bean会从池中移除)

第二种:setter方式单例,默认方式
修改配置文件为set方式注入,scope="singleton"表示单例,默认就是单例,可以不设置

    
    
        
    
    
        
    
    
        
    
1
2
3
4
5
6
7
8
9
10
下面是测试类:

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("com/zuoyueer/applicationContext.xml");
        System.out.println(context.getBean("a", StudentA.class));
    }
}
1
2
3
4
5
6
打印结果为:(正常的创建了对象,没有异常)

com.zuoyueer.StudentA@F646AE
1
分析原因:
容器安装配置顺序,先去实例化"StudentA",首先会根据无参构造器创建一个bean,并储存该对象的引用储存到容器中, 这个引用是"StudentA对象的一个早期的引用(early reference), 注意: 此时setter注入器还未被调用,这个对象是没有给属性赋值的.
再去实例化"StudentB",也会根据无参构造器创建一个bean,并把对象的早期的引用(early reference)储存到容器中.
再去实例化"StudentC", 因为"StudentC"依赖于"“StudentA” , 这时容器中有"StudentA"的对象的一个早期的引用(early reference),那么就调用setter注入器, 把"StudentA"对象的早期引用注入到"StudentC"对象中
以上过程是递归完成是,所以"StudentC"实例化之后,会接着调用"StudentB"对象的setter注入器,最后调用"StudentA"对象的setter注入器,完成对象中属性的赋值.也就是依赖注入,
为了理解这个早期的引用(early reference),我们需要了解Spring的三级缓存

缓存    用途
singletonObjects    用于存放完全初始化好的 bean,从该缓存中取出的 bean 可以直接使用
earlySingletonObjects    存放原始的 bean 对象(尚未填充属性),用于解决循环依赖
singletonFactories    存放 bean 工厂对象,用于解决循环依赖
实际上, 根据无参构造器创建一个bean,并将此类对应的ObjectFactory暴露出去,也就是注册到singletonFactories中, 从而使其他的bean能引用到该bean.当你依赖到了该Bean而单例缓存里面有没有该Bean的时候就会调用该工厂方法生产Bean,此时setter注入器还未被调用

为什么不把Bean直接暴露出去,而是暴露个Factory呢?因为有些Bean是需要被代理的.

以上过程最关键的一点是Spring是先将Bean对象实例化(无参构造)之后再设置对象属性的

关于3级缓存,推荐一个博主的博客:https://blog.youkuaiyun.com/f641385712/article/details/92801300

第三种:setter方式原型,prototype,也就是多例
修改配置文件为:```scope=“prototype”``表示多例,也就是每次请求的时候都创建一个新的对象

    
        
    
    
        
    
    
        
    
1
2
3
4
5
6
7
8
9
打印结果:

Caused by: 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
分析原因:
过程和第二种方式差不多,只不过,在调用setter注入器的时候,在缓存中找不到被暴露出来的工厂对象(找不到依赖的早期引用).
这是因为“prototype”作用域的Bean,Spring容器不进行缓存,因此无法提前暴露一个创建中的Bean。

多例模式是几乎不会用到的,在用的时候也要避免循环依赖, 并且对于"singleton"作用于的bean,我们可以通过"setAllowCircularReferences(false)"来禁止循环引用.

总结:
对于Spring循环依赖的情况总结如下:
不能解决的情况:

. 构造器注入循环依赖
. prototype field属性注入循环依赖(多例)
能解决的情况:

. field属性注入(setter方法注入)循环依赖(单例)
————————————————
版权声明:本文为优快云博主「佐月儿」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.youkuaiyun.com/优快云_zuoyueer/article/details/103312503

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值