高级java每日一道面试题-2024年10月7日-框架篇[springboot篇]-springboot如何处理循环依赖的问题?

如果有遗漏,评论区告诉我进行补充

面试官: springboot如何处理循环依赖的问题?

我回答:

循环依赖的概念

当两个或多个 Bean 相互依赖对方,就形成了循环依赖。例如,Bean A 依赖 Bean B,同时 Bean B 又依赖 Bean A。这会导致应用程序无法正确地初始化和运行,因为Spring Boot无法处理这种循环依赖关系。

Spring Boot 处理循环依赖的机制

Spring容器在启动时,会扫描配置文件(如applicationContext.xml)或注解定义的Bean,并尝试创建这些Bean的实例。在这个过程中,Spring会跟踪哪些Bean正在被创建,以便检测循环依赖。Spring通过一个名为“DefaultSingletonBeanRegistry”的类来跟踪单例Bean的创建状态,并使用三个主要的缓存来管理Bean的创建过程:

三级缓存机制

  1. 一级缓存:singletonObjects
    • 这是一个ConcurrentHashMap,用于存放完全初始化好的单例 Bean。当一个 Bean 完成创建和初始化(包括属性注入等所有步骤)后,就会被放入这个缓存中。
  2. 二级缓存:earlySingletonObjects
    • 也是一个ConcurrentHashMap。当一个 Bean 正在创建过程中(还未完全初始化),但是已经被创建了实例(通过构造函数创建),就会被放入这个缓存。这个缓存主要是为了解决循环依赖中的半成品 Bean 的暴露问题。
  3. 三级缓存:singletonFactories
    • 这是一个ConcurrentHashMap,存放的是ObjectFactory对象。当一个 Bean 开始创建时,会将创建这个 Bean 的ObjectFactory放入这个缓存。ObjectFactory是一个函数式接口,它的作用是在需要的时候创建 Bean 实例。

创建过程中的循环依赖处理

  1. Bean A 的创建过程
    • 当容器开始创建 Bean A 时,首先会将 Bean A 的创建工厂(ObjectFactory)放入三级缓存singletonFactories
    • 然后进行 Bean A 的实例化,通过构造函数创建出 Bean A 的实例,但此时 Bean A 还未完成属性注入等初始化操作。
    • 当进行 Bean A 的属性注入时,发现依赖 Bean B。
  2. Bean B 的创建过程
    • 容器开始创建 Bean B,同样先将 Bean B 的创建工厂放入三级缓存singletonFactories
    • 实例化 Bean B,在对 Bean B 进行属性注入时发现依赖 Bean A。
    • 此时容器会在三级缓存中查找 Bean A 的创建工厂,通过这个工厂得到 Bean A 的早期实例(半成品实例,还未完全初始化),将这个早期实例放入二级缓存earlySingletonObjects,并从三级缓存中移除 Bean A 的创建工厂。
    • Bean B 完成属性注入(其中包含了注入 Bean A 的早期实例)和其他初始化操作,成为一个完全初始化的 Bean,放入一级缓存singletonObjects
  3. Bean A 继续创建过程
    • 由于 Bean B 已经完成创建并注入到 Bean A 中,Bean A 可以继续完成自己的属性注入(此时注入的 Bean B 是完全初始化的)和其他初始化操作,成为一个完全初始化的 Bean,放入一级缓存singletonObjects

示例代码说明

以下是一个简单的示例代码来演示循环依赖的情况:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

@Component
public class ClassA {
    private ClassB classB;

    @Autowired
    public ClassA(ClassB classB) {
        this.classB = classB;
    }
}

@Component
public class ClassB {
    private ClassA classA;

    // 使用@Lazy注解可以延迟加载,避免在创建ClassB时立即触发循环依赖的初始化问题
    @Autowired
    public ClassB(@Lazy ClassA classA) {
        this.classA = classA;
    }
}

在上述代码中,ClassA依赖ClassBClassB又依赖ClassA。通过 Spring Boot 的循环依赖处理机制,即使存在这种循环依赖关系,也能够正确地创建和初始化这两个 Bean。

需要注意的是,虽然 Spring Boot 能够处理循环依赖,但循环依赖通常是一种不良的设计模式,可能会导致代码难以理解和维护,在实际开发中应尽量避免。

Spring Boot中处理循环依赖的方法

在Spring Boot中处理循环依赖主要有以下几种方法:

重新设计:

  • 重新设计代码结构,消除循环依赖是最理想的解决方案。

使用@Lazy注解:

  • 通过延迟加载依赖对象来解决循环依赖问题。在注入依赖时,先注入代理对象,当首次使用时再创建对象完成注入。

使用Setter/Field注入:

  • 在循环依赖的Bean中,使用Setter方法注入另一个Bean。
  • Spring可以先创建Bean的实例,然后再通过Setter方法进行依赖注入。
  • 对于Setter注入的循环依赖,Spring会从三级缓存或二级缓存中获取部分创建的Bean实例,提前暴露出来进行依赖注入,从而解决循环依赖问题。

构造器注入:

  • 通过构造函数的方式将循环依赖的Bean注入到另一个Bean中。
  • 但需要注意的是,构造器注入在循环依赖的情况下会抛出异常,因为构造器注入是一次性完成的,无法解决循环依赖的问题。

使用@Autowired和@Qualifier注解:

  • 在循环依赖的Bean中,使用@Autowired注解注入另一个Bean,并使用@Qualifier注解指定要注入的Bean的名称。
  • 这种方式可以解决由于多个相同类型的Bean导致的循环依赖问题。

使用@PostConstruct注解:

  • 在Bean的@PostConstruct方法中手动设置依赖关系。

实现ApplicationContextAwareInitializingBean接口:

  • 通过这些接口获取BeanFactory或ApplicationContext,并在适当的时机设置依赖关系。

配置允许循环引用:

  • 在Spring配置中设置spring.main.allow-circular-references=true来允许循环引用的存在。

注意事项

  1. 在处理循环依赖时,应尽量避免使用构造器注入,而是采用Setter注入或@Lazy注解等方式。
  2. 如果循环依赖的Bean中存在单例和原型模式的Bean同时存在的情况下,Spring会抛出异常。因为在创建Bean的时候无法确定它们的依赖关系。为了解决这个问题,可以将其中一个Bean的作用域改为原型模式,或者使用代理的方式解决循环依赖。
  3. 在实际开发中,最好设计出避免循环依赖关系的代码结构,从而保持代码的清晰和可维护性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

java我跟你拼了

您的鼓励是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值