Spring 能解决所有循环依赖吗?

2023年 7月 18日 36.5k 0

以下内容基于 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 默认无法解决的循环依赖,其实也不是无法解决,需要一些额外配置也能解决,当然,这些额外配置并非本文重点,松哥后面再来和大家介绍~

    另外最近两篇关于循环依赖的文章都还没有涉及到源码分析,大家先把思路整清楚,后面松哥再出源码分析的文章~

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论