你知道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解决循环依赖内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Data Source与数据库连接池简介(JDBC简介)

    Data Source与数据库连接池简介(JDBC简介)

    DataSource是作为DriverManager的替代品而推出的,DataSource 对象是获取连接的首选方法,这篇文章主要介绍了Data Source与数据库连接池简介(JDBC简介),需要的朋友可以参考下
    2022-11-11
  • Java toString方法使用超全讲解

    Java toString方法使用超全讲解

    在Java中所有的类都有一个共同的父类Object(默认继承,不用加extends关键字),toString方法就是Object类的一个方法,用来返回该对象的字符串表示形式,下面这篇文章主要给大家介绍了关于Java toString方法使用的相关资料,需要的朋友可以参考下
    2023-05-05
  • 在项目中集成jetty server步骤解析

    在项目中集成jetty server步骤解析

    这篇文章主要介绍了在项目中集成jetty server步骤解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-02-02
  • Java新手环境搭建 Tomcat安装配置教程

    Java新手环境搭建 Tomcat安装配置教程

    这篇文章主要为大家详细介绍了Java新手环境搭建的相关资料,Tomcat安装配置教程,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-03-03
  • java 多线程饥饿现象的问题解决方法

    java 多线程饥饿现象的问题解决方法

    这篇文章主要介绍了java 多线程饥饿现象的问题解决方法的相关资料,需要的朋友可以参考下
    2017-06-06
  • Java 数据库连接池Druid 的介绍

    Java 数据库连接池Druid 的介绍

    这篇文章主要给大家分享的是 Java 数据库连接池Druid 的介绍,Druid是一个JDBC组件,它包括三部分: DruidDriver 代理Driver,能够提供基于Filter-Chain模式的插件体系。 DruidDataSource 高效可管理的数据库连接池,下面来看看文中的详细内容,需要的朋友也可以参考一下
    2021-11-11
  • Java使用Calendar类实现动态日历

    Java使用Calendar类实现动态日历

    这篇文章主要为大家详细介绍了Java使用Calendar类实现动态日历,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-07-07
  • Java实现文件点击没反应的方法

    Java实现文件点击没反应的方法

    jsp页面链接,点击访问action用IO流去下载服务器上的文件,问题是任凭怎么点击都没反应,日志也不报错。这篇文章给大家介绍Java实现文件点击没反应的方法,需要的朋友参考下吧
    2018-07-07
  • spring cloud gateway 全局过滤器的实现

    spring cloud gateway 全局过滤器的实现

    全局过滤器作用于所有的路由,不需要单独配置,我们可以用它来实现很多统一化处理的业务需求,这篇文章主要介绍了spring cloud gateway 全局过滤器的实现,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2019-03-03
  • Java使用Ajax异步上传文件

    Java使用Ajax异步上传文件

    使用Ajax上传文件的应用场景颇多,比如上传用户头像、博客文章中插入图片、对认证用户相关身份进行校验等等很多很多。本文提供一个简单的示例供大家参考
    2021-05-05

最新评论