Skip to main content
  1. posts/

Spring真的完全解决了循环依赖吗

·1180 words·3 mins

感觉好久没写过博客了,刚好最近遇到一个还挺有意思的问题,就记录一下吧。

现象

image.png
image.png
image.png

??? Spring不是通过三级缓存解决了循环依赖问题吗? 这不就是A依赖B,然后B依赖A吗?怎么也报循环依赖啊。

问题排查

排查思路

  1. 难道这个版本的Spring没解决?写个Demo验证一下(简单写个TestService1和TestService2相互依赖就行)。启动很成功啊。【不过这里还有一个比较好玩的现象,就是在TestService1的方法上加上@Async注解,就会报循环依赖了,这个google上有很多答案,其实就是TestService1被代理了,引用变了,然后TestService2还保留的是原始的引用】
  2. 把相关依赖去掉试试(把ConnectServiceImpl和UserServiceImpl中的其他依赖去掉试试看了),还是报错循环依赖。

源码分析

what’s the hell? 只能捋捋源码了。 看看Spring解决循环依赖的过程

当我发现初始化前后,这个ConnectService的引用不一样时,问题就已经浮出水面了。这个情况应该跟@Async的情况是一样的,就是UserService中注入了ConnectService的引用,然后后续ConnectService初始化的时候,被包装了,引用变了,然后ConnectService初始化会判断新的引用和旧的是否相同,如果不相同,会去找ConnectService实际上依赖的bean(userService)是否创建了,如果创建了就添加到依赖列表中,如果依赖列表不为空,就报错循环依赖。

image.png
那么问题来了,实例化的时候包装了ConnectService,引用变成新的了,但是正常来讲,不应该包装啊。 Test1和Test2相互引用也没有被包装啊。 那就来看看源码吧
image.png
image.png

可以看到,在初始化这个ConnectService类的时候,会调用一个applyBeanPostProcessorsAfterInitialization方法,这个方法的会应用所有的beanPostProcessor,其中有一个方法参数校验的beanPostProcessor会代理这个类。所以导致引用变了。 那罪魁祸首就出来了呀,就是这个MethodValidationPostProcessor会代理这个bean,然后修改引用。那为啥会被这个MethodValidationPostProcessor代理呢。当然是这个AOP啦。

image.png

解决

两种方案

  1. 去除@Validated校验。
  2. 注入UserService时加上@Lazy注解

方案一得加好多校验代码,选择方案二吧。

总结

循环依赖处理失效的场景

  1. 初始化过程生成了新的代理对象(一般应该是初始化过程中使用了一些前后置处理器)
  2. 使用@DependOn产生循环依赖
  3. 非单例产生循环依赖

虽然2,3在本案例中并没有提到,但是应该也是可以明白的

参考

https://zhuanlan.zhihu.com/p/344624139 (三级缓存分析的还比较清晰)