Springboot的bean的循环依赖问题

循环依赖指的是两个或多个 Bean 之间互相依赖,进而形成一个闭环。

一、循环依赖的示例

1. 构造器注入引发的循环依赖

当两个 Bean 通过构造器实现互相注入时,就会造成循环依赖。

@Component
public class A {
    private final B b;

    @Autowired
    public A(B b) {
        this.b = b;
    }
}

@Component
public class B {
    private final A a;

    @Autowired
    public B(A a) {
        this.a = a;
    }
}

问题分析

  • Spring 在创建 Bean A 的时候,需要先注入 Bean B。
  • 而创建 Bean B 时,又得先注入 Bean A。
  • 这样就形成了一个死循环,最终导致 BeanCurrentlyInCreationException 异常。
2. Setter 注入引发的循环依赖(Spring 可自动解决)
@Component
public class A {
    private B b;

    @Autowired
    public void setB(B b) {
        this.b = b;
    }
}

@Component
public class B {
    private A a;

    @Autowired
    public void setA(A a) {
        this.a = a;
    }
}

解决原理

  • Spring 采用三级缓存机制来处理 setter 注入的循环依赖。
  • 先将未完全初始化的 Bean 放入二级缓存,供其他 Bean 引用。
  • 等 Bean 完全初始化之后,再将其移至三级缓存。
3. 字段注入引发的循环依赖
@Component
public class A {
    @Autowired
    private B b;
}

@Component
public class B {
    @Autowired
    private A a;
}

说明
字段注入本质上和 setter 注入类似,Spring 也能够自动解决这种循环依赖问题。

二、循环依赖的解决办法

1. 重构代码,消除循环依赖

这是解决循环依赖问题的最佳方式。可以把公共逻辑提取出来,创建一个新的组件。

@Component
public class CommonService {
    public void doSomething() {
        // 公共逻辑
    }
}

@Component
public class A {
    private final CommonService commonService;

    @Autowired
    public A(CommonService commonService) {
        this.commonService = commonService;
    }
}

@Component
public class B {
    private final CommonService commonService;

    @Autowired
    public B(CommonService commonService) {
        this.commonService = commonService;
    }
}
2. 使用 @Lazy 注解延迟加载
@Component
public class A {
    private final B b;

    @Autowired
    public A(@Lazy B b) {
        this.b = b;
    }
}

作用

  • 在注入 Bean B 时,不直接创建实例,而是注入一个代理对象。
  • 等到实际使用 Bean B 的时候,才会去创建真正的实例。
3. 使用 setter 注入或者字段注入

正如前面示例所示,setter 注入和字段注入能够利用 Spring 的三级缓存机制来解决循环依赖问题。

三、循环依赖的检测与调试

1. 查看详细的错误日志

当出现循环依赖时,Spring 会在日志中明确指出循环依赖的路径。

Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
2. 使用 @DependsOn 注解

如果必须要控制 Bean 的创建顺序,可以使用 @DependsOn 注解。不过要谨慎使用,因为它可能会掩盖更深层次的设计问题。 

@Component
@DependsOn("b")
public class A {
    // ...
}

spring自动解决依赖问题请看这篇文章

https://blog.youkuaiyun.com/XiaobaiPlayer/article/details/148286719?spm=1001.2014.3001.5502 

<think>嗯,用户问的是如何解决Spring Boot中的循环依赖问题。首先,我需要回忆一下什么是循环依赖,以及为什么在Spring Boot中会出现这个问题循环依赖指的是两个或多个Bean相互依赖,比如Bean A依赖Bean B,而Bean B又反过来依赖Bean A。当Spring容器在初始化这些Bean的时候,会因为无法确定创建顺序而导致错误。 接下来,我应该考虑Spring处理Bean注入的方式,主要是构造器注入和Setter注入。可能用户在使用构造器注入时更容易遇到循环依赖问题,因为Spring在创建Bean时必须先解析所有构造器参数,而如果存在循环,容器可能无法解决。而Setter注入是在Bean实例化之后进行的,所以可能更容易处理这种情况。 然后,想到Spring Boot从2.6版本开始默认禁止了循环依赖,这可能也是用户遇到问题的原因之一。所以,解决方案之一可能是调整配置,允许循环依赖,但这只是临时的解决办法,毕竟最佳实践是避免循环依赖的设计。 接下来,具体的解决方法可能包括: 1. 重新设计代码结构,消除循环依赖。比如引入第三个类,把共同依赖的部分抽离出来,或者使用接口抽象。这部分需要用户有一定的设计能力,可能需要调整类之间的关系。 2. 使用@Lazy注解。在其中一个依赖上添加@Lazy,延迟加载,这样Spring在初始化的时候可以先创建一个代理,等真正需要的时候再注入实际的Bean。这样可以打破初始化时的循环。 3. 改用Setter注入而不是构造器注入。因为Setter注入允许Bean在实例化之后再进行依赖注入,这样Spring可以处理循环的情况。但可能需要注意方法是否被正确调用。 4. 调整Spring Boot的配置,允许循环依赖。比如在application.properties中设置spring.main.allow-circular-references=true。不过这种方法可能只是掩盖问题,而不是根本解决。 然后,我需要考虑用户的实际场景。比如用户可能是在升级Spring Boot版本后遇到这个问题,因为之前版本可能允许循环依赖,而新版本默认禁止。这时候用户可能需要快速解决问题,所以提供配置选项可能是一个快速解决方案,但同时也要提醒他们最好还是重构代码。 另外,用户可能对循环依赖的原因不太清楚,所以需要解释为什么会出现这个问题,以及各种解决方法的适用场景。例如,使用@Lazy可能在某些情况下有效,但过度使用可能导致其他问题,比如代理对象的异常。 可能还需要提到Spring的三级缓存机制如何处理循环依赖,这样用户可以更深入理解。例如,对于Setter注入,Spring通过提前暴露半成品的Bean来解决循环依赖,而构造器注入无法做到这一点,因为构造器需要完整的依赖。 最后,总结时应该强调避免循环依赖的重要性,尽管有临时解决办法,但良好的设计才是根本。同时,根据具体情况选择最合适的解决方案,比如在紧急情况下允许配置,但后续还是要重构代码。</think>在 Spring Boot 中,**循环依赖**(Circular Dependency)是指两个或多个 Bean 相互依赖,形成闭环,导致 Spring 容器无法正确初始化这些 Bean。以下是解决循环依赖问题的常见方法: --- ### 一、循环依赖的产生场景 例如:`BeanA` 依赖 `BeanB`,而 `BeanB` 又依赖 `BeanA`。 ```java // BeanA @Component public class BeanA { private final BeanB beanB; public BeanA(BeanB beanB) { this.beanB = beanB; } // 构造器注入 } // BeanB @Component public class BeanB { private final BeanA beanA; public BeanB(BeanA beanA) { this.beanA = beanA; } // 构造器注入 } ``` 此时 Spring 会抛出 `BeanCurrentlyInCreationException`。 --- ### 二、解决方法 #### 1. **重构代码(推荐)** - **最佳实践**是避免循环依赖,通过重新设计代码结构: - 提取公共逻辑到第三个类。 - 使用接口或抽象类解耦。 - 使用事件驱动(如 `ApplicationEvent`)替代直接依赖。 #### 2. **使用 `@Lazy` 延迟加载** - 在其中一个 Bean 的依赖上添加 `@Lazy`,告知 Spring 延迟初始化该 Bean: ```java @Component public class BeanA { private final BeanB beanB; public BeanA(@Lazy BeanB beanB) { this.beanB = beanB; // 延迟注入 } } ``` #### 3. **改用 Setter/Field 注入** - **构造器注入**容易导致循环依赖,改用 Setter 或 Field 注入: ```java @Component public class BeanA { private BeanB beanB; @Autowired // Setter 注入 public void setBeanB(BeanB beanB) { this.beanB = beanB; } } ``` #### 4. **配置允许循环依赖(临时方案)** - 在 `application.properties` 中启用循环依赖Spring Boot 2.6+ 默认禁用): ```properties spring.main.allow-circular-references=true ``` - **注意**:这只是绕过问题,不推荐长期使用。 #### 5. **使用 `@PostConstruct`** - 通过 `@PostConstruct` 在 Bean 初始化后手动注入依赖: ```java @Component public class BeanA { private BeanB beanB; @Autowired public void setBeanB(BeanB beanB) { this.beanB = beanB; } } @Component public class BeanB { private BeanA beanA; @Autowired public void setBeanA(BeanA beanA) { this.beanA = beanA; } @PostConstruct public void init() { beanA.setBeanB(this); // 手动关联 } } ``` --- ### 三、Spring 如何解决循环依赖Spring 通过 **三级缓存** 处理单例 Bean循环依赖: 1. **一级缓存**(`singletonObjects`):存放完全初始化的 Bean。 2. **二级缓存**(`earlySingletonObjects`):存放提前暴露的未完成初始化的 Bean。 3. **三级缓存**(`singletonFactories`):存放 Bean 的工厂对象,用于生成提前暴露的 Bean。 当检测到循环依赖时,Spring 会提前暴露一个“半成品” Bean,完成后续注入。 --- ### 四、注意事项 1. **构造器注入**无法通过三级缓存解决循环依赖,优先使用 Setter/Field 注入。 2. 尽量避免循环依赖,它是代码设计上的“坏味道”。 3. 若使用 `@Lazy`,需注意代理对象可能引发的潜在问题(如 `NullPointerException`)。 通过合理设计或上述方法,可以有效解决 Spring Boot 中的循环依赖问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值