你知道Spring如何解决所有循环依赖的吗

 更新时间:2023年07月20日 10:20:02   作者:江南一点雨  
这篇文章主要来和大家聊聊Spring 能解决所有循环依赖吗,文中的示例代码讲解详细,对我们学习Spring有一定的帮助,需要的小伙伴可以参考一下

以下内容基于 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如何解决所有循环依赖的吗的文章就介绍到这了,更多相关Spring解决循环依赖内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • java substring 截取字符串的方法

    java substring 截取字符串的方法

    这篇文章主要介绍了java substring 截取字符串的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-05-05
  • 使用IntelliJ IDEA创建简单的Java Web项目完整步骤

    使用IntelliJ IDEA创建简单的Java Web项目完整步骤

    这篇文章主要介绍了如何使用IntelliJ IDEA创建一个简单的JavaWeb项目,实现登录、注册和查看用户列表功能,使用Servlet和JSP技术,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2025-01-01
  • SpringBoot中JSON工具类JsonUtils的实践教学

    SpringBoot中JSON工具类JsonUtils的实践教学

    Spring Boot 项目里,JSON 工具类是每个 Service 都要写的东西,JsonUtils的核心是一个统一接口覆盖三套 JSON 引擎,本文主要介绍了JsonUtils在SpringBoot项目中的具体应用,希望对大家有所帮助
    2026-06-06
  • 你还在用Synchronized?Atomic你了解不?

    你还在用Synchronized?Atomic你了解不?

    这篇文章主要介绍了你还在用Synchronized?Atomic你了解不? 文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2018-12-12
  • SpringBoot如何使用注解进行XSS防御

    SpringBoot如何使用注解进行XSS防御

    在SpringBoot中,可以通过自定义@XSS注解和实现XSSValidator类来防御XSS攻击,此方法适用于GET和POST请求,通过在方法参数或实体类属性上添加@XSS注解,并结合@Valid或@Validated注解使用,有效拦截潜在的XSS脚本,保障应用安全
    2024-11-11
  • IDEA:Git stash 暂存分支修改的实现代码

    IDEA:Git stash 暂存分支修改的实现代码

    这篇文章主要介绍了IDEA:Git stash 暂存分支修改的实现代码,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-03-03
  • Java字符串工具类从原生到第三方库的高效实践详解

    Java字符串工具类从原生到第三方库的高效实践详解

    字符串是我们在编程中最常使用的一种数据类型,Java中用类来描述字符串,其中最常用的字符串处理类是String,这篇文章主要介绍了Java字符串工具类从原生到第三方库的高效实践的相关资料,需要的朋友可以参考下
    2025-08-08
  • 快速解决Hash碰撞冲突的方法小结

    快速解决Hash碰撞冲突的方法小结

    这篇文章主要介绍了快速解决Hash碰撞冲突的方法小结,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • 简单了解java中int和Integer的区别

    简单了解java中int和Integer的区别

    这篇文章主要介绍了简单了解java中int和Integer的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-10-10
  • 图解Java ReentrantLock的条件变量Condition机制

    图解Java ReentrantLock的条件变量Condition机制

    想必大家都使用过wait()和notify()这两个方法把,他们主要用于多线程间的协同处理。而RenentrantLock也支持这样条件变量的能力,而且相对于synchronized 更加强大,能够支持多个条件变量,本文就来详细说说
    2022-10-10

最新评论