为什么会有循环依赖?Spring 为什么允许两个 Bean 互相引用
为什么会有循环依赖?Spring 为什么允许两个 Bean 互相引用?
上一篇我们分析了:
《@Transactional 为什么会失效?一次 Debug 看懂 Spring 踩过的所有坑》
有读者留言:
既然 Spring 负责创建 Bean、注入 Bean、生成代理对象。
那如果两个 Bean 互相依赖怎么办?
例如:
@Service
public class AService {
@Autowired
private BService bService;
}
@Service
public class BService {
@Autowired
private AService aService;
}
A 依赖 B。
B 又依赖 A。
这不就是死循环吗?
正常情况下不是应该直接报错吗?
但神奇的是:
Spring 居然允许这种情况存在。
为什么?
很多文章一上来就讲三级缓存。
结果大家背了半天:
一级缓存
二级缓存
三级缓存
最后连循环依赖到底是怎么产生的都没搞明白。
所以这一篇我们先不急着看三级缓存。
先跟一次源码。
看看循环依赖到底是在什么地方产生的。
什么是循环依赖?
所谓循环依赖。
本质上就是:
A 依赖 B
B 依赖 A
形成一个闭环。
例如:
@Service
public class UserService {
@Autowired
private OrderService orderService;
}
@Service
public class OrderService {
@Autowired
private UserService userService;
}
这种情况就属于循环依赖。
很多人觉得:
这是代码写错了。
实际上在大型项目里非常常见。
因为业务之间本来就存在关联。
用户查订单。
订单查用户。
写着写着就形成了依赖闭环。
如果没有 Spring 会怎样?
假设我们自己写代码:
AService a = new AService();
BService b = new BService();
a.setBService(b);
b.setAService(a);
是不是完全没问题?
因为对象已经创建出来了。
后面再互相引用即可。
所以循环依赖真正的问题不是:
A 引用 B
B 引用 A
而是:
创建 A 的时候需要 B
创建 B 的时候又需要 A
这才是 Spring 面临的问题。
从源码开始 Debug
项目启动时。
Spring 会先创建 Bean。
入口最终会来到:
AbstractBeanFactory#doGetBean()
继续进入:
AbstractAutowireCapableBeanFactory#createBean()
再往下:
AbstractAutowireCapableBeanFactory#doCreateBean()
这里就是 Bean 创建的核心方法。
打个断点。
看看里面做了什么。
首先:
BeanWrapper instanceWrapper =
createBeanInstance(beanName, mbd, args);
执行完之后。
AService 已经实例化完成。
相当于:
AService aService = new AService();
此时对象已经存在。
但是:
@Autowired 还没执行
@PostConstruct 还没执行
初始化方法还没执行
也就是说:
对象创建了
但还没有初始化完成
真正的问题出现了
继续往下走。
会进入:
populateBean(beanName, mbd, instanceWrapper);
看到这个名字就能猜出来。
populate
填充
这里开始处理依赖注入。
例如:
@Autowired
private BService bService;
Spring 发现需要 BService。
于是再次调用:
getBean("bService");
开始创建 BService。
流程再次进入:
doCreateBean()
然后:
createBeanInstance()
实例化 BService。
接着:
populateBean()
开始注入属性。
结果发现:
@Autowired
private AService aService;
于是再次执行:
getBean("aService");
此时流程变成:
getBean(A)
↓
createBean(A)
↓
populateBean(A)
↓
getBean(B)
↓
createBean(B)
↓
populateBean(B)
↓
getBean(A)
看到这里。
问题就来了。
为什么没有无限递归?
按正常逻辑:
A 创建需要 B
B 创建需要 A
A 创建需要 B
B 创建需要 A
...
应该无限循环。
最后:
StackOverflowError
才对。
但 Spring 并没有。
项目依然启动成功。
为什么?
关键在于:
当第二次执行:
getBean("aService")
时。
Spring 并没有重新创建 AService。
而是用了另外一种办法。
Spring 发现了一个关键点
仔细观察刚才的流程。
第一次创建 AService 时:
createBeanInstance()
已经执行过了。
也就是说:
new AService()
其实已经完成。
虽然:
属性还没注入
初始化还没完成
但对象已经存在。
于是 Spring 想到一个办法:
既然对象已经创建出来了
能不能提前拿出来?
如果可以:
创建 A
↓
提前暴露 A
↓
创建 B
↓
B 拿到 A
↓
B 创建完成
↓
继续完成 A
循环依赖问题似乎就解决了。
真正难的地方是什么?
很多人以为:
循环依赖难点是:
A 引用 B
B 引用 A
其实不是。
真正难的是:
A 还没有初始化完成
Spring 能不能把它提前交给别人使用?
如果只是普通对象。
似乎问题不大。
但别忘了。
Spring 还有:
@Transactional
@Async
AOP
这些功能最终都会生成代理对象。
问题来了。
提前暴露出去的到底是:
原始对象?
还是:
代理对象?
如果处理不好。
最终可能出现:
B 里面拿到的是原始对象
Spring 容器保存的是代理对象
那整个框架就乱了。
所以循环依赖真正要解决的从来不是:
A 引用 B
B 引用 A
而是:
对象还没初始化完成
Spring 如何提前暴露它
并保证最终对象一致
总结
今天我们没有讲三级缓存。
而是先定位问题。
通过源码可以看到:
循环依赖产生的位置就在:
doCreateBean()
中的:
populateBean()
当 A 创建过程中需要 B。
B 创建过程中又需要 A。
循环依赖就出现了。
真正的问题是:
A 已经实例化
但还没有初始化完成
Spring 为什么还能把它交给 B 使用?
答案就藏在下一篇的源码里。
上一篇:
《@Transactional 为什么会失效?一次 Debug 看懂 Spring 踩过的所有坑》
下一篇:
《Spring 为什么必须三级缓存?看懂三级缓存才真正理解循环依赖》
如果这篇对你有帮助,欢迎点赞、收藏。
你在项目里遇到过循环依赖吗?
评论区聊聊。
更多推荐



所有评论(0)