Spring循环依赖,竟然有这样不可思议的坑!
Posted 互联网架构师
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring循环依赖,竟然有这样不可思议的坑!相关的知识,希望对你有一定的参考价值。
来源:t.cn/AisBoCfE
00 前言
这两天工作遇到了一个挺有意思的Spring循环依赖的问题,但是这个和以往遇到的循环依赖问题都不太一样,隐藏的相当隐蔽,网络上也很少看到有其他人遇到类似的问题。这里权且称他非典型Spring循环依赖问题。但是我相信我肯定不是第一个踩这个坑的,也一定不是最后一个,可能只是因为踩过的人比较少、鲜有记录罢了。因此这里权且记录一下这个坑,方便后人查看。
正如鲁迅(我)说过,“这个世上本没有坑,踩的人多了,也便成了坑”。
01 正文
-
1
-
典型场景
-
2
-
什么是依赖
A强依赖B。创建A的实例这件事情本身需要B来参加。对照在现实生活就像妈妈生你一样。
A弱依赖B。创建A的实例这件事情不需要B来参加,但是A实现功能是需要调用B的方法。对照在现实生活就像男耕女织一样。
强依赖之间的循环依赖。
弱依赖之间的循环依赖。
-
3
-
什么是依赖调解
-
4
-
为什么要依赖注入
-
5
-
Spring的依赖注入模型
类的构造,调用构造函数、解析强依赖(一般是无参构造),并创建类实例。
类的配置,根据Field/GetterSetter中的依赖注入相关注解、解析弱依赖,并填充所有需要注入的类。
类的初始化逻辑,调用生命周期中的初始化方法(例如
@PostConstruct
注解或InitializingBean
的afterPropertiesSet
方法),执行实际的初始化业务逻辑。
为什么Spring除了构造函数之外还要在Bean生命周期里有一个额外的初始化方法?
这个初始化方法和构造函数到底有什么区别?
为什么Spring建议将初始化的逻辑写在生命周期里的初始化方法里?
为了进行依赖调解,Spring在调用构造函数时是没有将依赖注入进来的。也就是说构造函数中是无法使用通过DI注入进来的bean(或许可以,但是Spring并不保证这一点)。
如果不在构造函数中使用依赖注入的bean而仅仅使用构造函数中的参数,虽然没有问题,但是这就导致了这个bean强依赖于他的入参bean。当后续出现循环依赖时无法进行调解。
-
6
-
非典型问题
通过构造函数传递依赖的做法是有可能造成无法自动调解的循环依赖的。
纯粹通过Field/GetterSetter进行依赖注入造成的循环依赖是完全可以被自动调解的。
问题
@SpringBootApplication
@Import({ServiceA.class, ConfigurationA.class, BeanB.class})
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
public class ServiceA {
@Autowired
private BeanA beanA;
@Autowired
private BeanB beanB;
}
public class ConfigurationA {
@Autowired
public BeanB beanB;
@Bean
public BeanA beanA() {
return new BeanA();
}
}
public class BeanA {
}
public class BeanB {
@Autowired
public BeanA beanA;
}
@Component
、
@Configuration
之类的注解,而是采用
@Import
手动扫描Bean是为了方便指定Bean的初始化顺序。Spring会按照我
@Import
的顺序依次加载Bean。同时,在加载每个Bean的时候,如果这个Bean有需要注入的依赖,则会试图加载他依赖的Bean。
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'beanA': Requested bean is currently in creation: Is there an unresolvable circular reference?
ServiceA
类中声明的
BeanA
,
BeanB
调换一下位置,你就会发现这段代码突然就跑的通了!!!
解释
ConfigurationA
这个配置类。
先加载BeanA
当Spring在试图加载ServiceA时,先构造了ServiceA,然后发现他依赖BeanA,于是就试图去加载BeanA;
Spring想构造BeanA,但是发现BeanA在ConfigurationA内部,于是又试图加载ConfigurationA(此时BeanA仍未构造);
Spring构造了ConfigurationA的实例,然后发现他依赖BeanB,于是就试图去加载BeanB。
Spring构造了BeanB的实例,然后发现他依赖BeanA,于是就试图去加载BeanA。
Spring发现BeanA还没有实例化,此时Spring发现自己回到了步骤2。。。GG。。。
先加载BeanB
当Spring在试图加载ServiceA时,先构造了ServiceA,然后发现他依赖BeanB,于是就试图去加载BeanB;
Spring构造了BeanB的实例,然后发现他依赖BeanA,于是就试图去加载BeanA。
Spring发现BeanA在ConfigurationA内部,于是试图加载ConfigurationA(此时BeanA仍未构造);
Spring构造了ConfigurationA的实例,然后发现他依赖BeanB,并且BeanB的实例已经有了,于是将这个依赖填充进ConfigurationA中。
Spring发现ConfigurationA已经完成了构造、填充了依赖,于是想起来构造了BeanA。
Spring发现BeanA已经有了实例,于是将他给了BeanB,BeanB填充的依赖完成。
Spring回到了为ServiceA填充依赖的过程,发现还依赖BeanA,于是将BeanA填充给了ServiceA。
Spring成功完成了初始化操作。
结论
代码坏味道
1、
2、
3、
4、
5、
6、
7、
8、
以上是关于Spring循环依赖,竟然有这样不可思议的坑!的主要内容,如果未能解决你的问题,请参考以下文章