以下内容基于 Spring6.0.4。
看了上篇文章的小伙伴,对于 Spring 解决循环依赖的思路应该有一个大致了解了,今天我们再来看一看,按照上篇文章介绍的思路,有哪些循环依赖 Spring 处理不了。
严格来说,其实也不是解决不了,所有问题都有办法解决,只是还需要额外配置,这个不是本文的主题,松哥后面再整文章和小伙伴们细聊。
1. 基于构造器注入
如果依赖的对象是基于构造器注入的,那么执行的时候就会报错,代码如下:
@Service
public class AService {
BService bService;
public AService(BService bService) {
this.bService = bService;
}
}
@Service
public class BService {
AService aService;
public BService(AService aService) {
this.aService = aService;
}
}
运行时报错如下:
原因分析:
上篇文章我们说解决循环依赖的思路是加入缓存,如下图:
我们说先把 AService 原始对象创建出来,存入到缓存池中,然后再处理 AService 中需要注入的外部 Bean 等等,但是,如果 AService 依赖的 BService 是通过构造器注入的,那就会导致在创建 AService 原始对象的时候就需要用到 BService,去创建 BService 时候又需要 AService,这样就陷入到死循环了,对于这样的循环依赖执行时候就会出错。
更进一步,如果我们在 AService 中是通过 @Autowired 来注入 BService 的,那么应该是可以运行的,代码如下:
@Service
public class AService {
@Autowired
BService bService;
}
@Service
public class BService {
AService aService;
public BService(AService aService) {
this.aService = aService;
}
}
上面这段代码,AService 的原始对象就可以顺利创建出来放到缓存池中,BService 创建所需的 AService 也就能从缓存中获取到,所以就可以执行了。
2. prototype 对象
循环依赖双方 scope 都是 prototype 的话,也会循环依赖失败,代码如下:
@Service
@Scope("prototype")
public class AService {
@Autowired
BService bService;
}
@Service
@Scope("prototype")
public class BService {
@Autowired
AService aService;
}
这种循环依赖运行时也会报错,报错信息如下(跟前面报错信息一样):
原因分析:
scope 为 prototype 意思就是说这个 Bean 每次需要的时候都现场创建,不用缓存里的。那么 AService 需要 BService,所以就去现场创建 BService,结果 BService 又需要 AService,继续现场创建,AService 又需要 BService…,所以最终就陷入到死循环了。
3. @Async
带有 @Async 注解的 Bean 产生循环依赖,代码如下:
@Service
public class AService {
@Autowired
BService bService;
@Async
public void hello() {
}
}
@Service
public class BService {
@Autowired
AService aService;
}
报错信息如下:
其实大家从这段报错信息中也能看出来个七七八八:在 BService 中注入了 AService 的原始对象,但是 AService 在后续的处理流程中被 AOP 代理了,产生了新的对象,导致 BService 中的 AService 并不是最终的 AService,所以就出错了!
那有小伙伴要问了,上篇文章我们不是说了三级缓存就是为了解决 AOP 问题吗,为什么这里发生了 AOP 却无法解决?
如下两个前置知识大家先理解一下:
第一:
其实大部分的 AOP 循环依赖是没有问题的,这个 @Async 只是一个特例,特别在哪里呢?一般的 AOP 都是由 AbstractAutoProxyCreator 这个后置处理器来处理的,通过这个后置处理器生成代理对象,AbstractAutoProxyCreator 后置处理器是 SmartInstantiationAwareBeanPostProcessor 接口的子类,并且 AbstractAutoProxyCreator 后置处理器重写了 SmartInstantiationAwareBeanPostProcessor 接口的 getEarlyBeanReference 方法;而 @Async 是由 AsyncAnnotationBeanPostProcessor 来生成代理对象的,AsyncAnnotationBeanPostProcessor 也是 SmartInstantiationAwareBeanPostProcessor 的子类,但是却没有重写 getEarlyBeanReference 方法,默认情况下,getEarlyBeanReference 方法就是将传进来的 Bean 原封不动的返回去。
第二:
在 Bean 初始化的时候,Bean 创建完成后,后面会执行两个方法:
- populateBean:这个方法是用来做属性填充的。
- initializeBean:这个方法是用来初始化 Bean 的实例,执行工厂回调、init 方法以及各种 BeanPostProcessor。
大家先把这两点搞清楚,然后我来跟大家说上面代码的执行流程。
- 首先 AService 初始化,初始化完成之后,存入到三级缓存中。
- 执行 populateBean 方法进行 AService 的属性填充,填充时发现需要用到 BService,于是就去初始化 BService。
- 初始化 BService 发现需要用到 AService,于是就去缓存池中找,找到之后拿来用,但是!!!这里找到的 AService 不是代理对象,而是原始对象。因为在三级缓存中保存的 AService 的那个 ObjectFactory 工厂,在对 AService 进行提前 AOP 的时候,执行的是 SmartInstantiationAwareBeanPostProcessor 类型的后置处理器 中的 getEarlyBeanReference 方法,如果是普通的 AOP,调用 getEarlyBeanReference 方法最终会触发提前 AOP,但是,这里执行的是 AsyncAnnotationBeanPostProcessor 中的 getEarlyBeanReference 方法,该方法只是返回了原始的 Bean,并未做任何额外处理。
- 当 BService 创建完成后,AService 继续初始化,继续执行 initializeBean 方法。
- 在 initializeBean 方法中,执行其他的各种后置处理器,包括 AsyncAnnotationBeanPostProcessor,此时调用的是 AsyncAnnotationBeanPostProcessor 的 postProcessAfterInitialization 方法,在该方法中为 AService 生成了代理对象。
- 在 initializeBean 方法执行完成之后,AService 会继续去检查最终的 Bean 是不是还是一开始的 Bean,如果不是,就去检查当前 Bean 有没有被其他 Bean 引用过,如果被引用过,就会抛出来异常,也就是上图大家看到的异常信息。
好啦,这就是松哥和大家分享的三种 Spring 默认无法解决的循环依赖,其实也不是无法解决,需要一些额外配置也能解决,当然,这些额外配置并非本文重点,松哥后面再来和大家介绍~
另外最近两篇关于循环依赖的文章都还没有涉及到源码分析,大家先把思路整清楚,后面松哥再出源码分析的文章~