浩哥笔记

我们的征途是星辰大海

  menu
40 文章
3364 浏览
1 当前访客
ღゝ◡╹)ノ❤️

Spring的循环依赖解决策略 置顶!

什么是spring循环依赖问题?

在Spring框架中,循环依赖问题指的是在依赖注入时,由于Bean之间相互引用而导致的初始化问题。

这种情况下,Spring容器在创建Bean的过程中,发现Bean A依赖于Bean B,而Bean B又依赖于Bean A,形成了循环依赖关系。


循环依赖的三种情况:

1.构造器循环依赖:

当两个或多个Bean的构造函数相互依赖时,会形成构造器循环依赖。这种情况下,Spring容器在创建Bean时无法确定哪个Bean应该先被实例化,因为它们相互依赖于彼此的构造函数参数。

示例:

public class A {
  private B b;

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

public class B {
  private A a;

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

2.属性循环依赖:

当两个或多个Bean的属性相互依赖时,会形成属性循环依赖。例如,Bean A依赖于Bean B的属性,而Bean B又依赖于Bean A的属性,形成了属性循环依赖。

示例:

public class A {
  private B b;

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

public class B {
  private A a;

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

3.单例Bean的循环依赖:

当单例Bean之间相互依赖时,会形成单例Bean的循环依赖。由于Spring默认情况下会将单例Bean存储在容器中,这种循环依赖问题可能会导致死锁或无限递归调用。

示例 :

假设我们有两个类 AB,它们相互依赖:

@Service
public class A {
  @Autowired
  private B b;
}

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

假设 A 类的一个实例需要依赖 B类的一个实例,而 B 类的一个实例又需要依赖 A 类的一个实例,形成了循环依赖。在这种情况下,如果我们试图使用 Spring 容器来管理这些类的实例,就会出现循环依赖的问题。

在初始化这些 Bean 时,Spring 会发现 A 类的实例需要 B 类的实例,而 B 类的实例又需要 A 类的实例,这样就形成了循环依赖。如果不采取措施来解决这个问题,Spring 容器就会陷入死循环或者抛出异常。


如何解决?

这三种循环依赖问题在Spring中都有解决方案:

1.构造器循环依赖解决方案:

可以通过使用 @Lazy注解延迟初始化其中一个依赖,或者使用@Autowired@Qualifier来指定构造器参数的具体Bean。

示例代码:

@Component
public class A {
  private B b;

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

@Component
public class B {
  private A a;

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

2. 属性循环依赖解决方案:

可以通过@Lazy注解延迟初始化其中一个依赖,或者将其中一个依赖设置为@Nullable,允许属性为空。

示例代码:

@Component
public class A {
  private B b;

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

@Component
public class B {
  @Nullable
  private A a;
  public void setA(A a) {
      this.a = a;
  }
}

3. 单例Bean的循环依赖解决方案:

可以通过使用 @Lazy 注解延迟初始化其中一个依赖,或者使用代理对象来解决单例Bean的循环依赖。

示例代码:

<bean id="a" class="com.example.A" lazy-init="true">
  <property name="b" ref="b"/>
</bean>

<bean id="b" class="com.example.B" lazy-init="true">
  <property name="a">
      <bean class="org.springframework.beans.factory.config.BeanReferenceFactoryBean">
          <property name="beanName" value="a"/>
      </bean>
  </property>
</bean>

这些解决方案可以根据具体情况选择适合的方式来解决Spring中的循环依赖问题。


三级缓存机制

为了解决这个问题,Spring使用了三级缓存机制。这三级缓存分别是:

  1. 存储单例Bean池(Singleton Bean Cache) :这是第一级缓存,用于存储完全初始化好的Bean。当Bean完成所有初始化步骤后,会被放入这个缓存中。
  2. 早期对象缓存(Early Singleton Objects Cache) :这是第二级缓存,用于存储早期暴露的对象。在Bean的属性填充阶段,如果Bean已经完成了属性填充但尚未完成初始化,那么它的早期对象会被放入这个缓存中。这样,其他Bean在初始化时可以引用到这个早期对象,从而解决循环依赖问题。
  3. 单例工厂缓存(Singleton Factories Cache) :这是第三级缓存,用于存储创建Bean的工厂对象。当Spring开始创建Bean时,它会首先检查这个缓存中是否已经有了对应的工厂对象。如果有,那么直接使用这个工厂对象来创建Bean;如果没有,则创建一个新的工厂对象并将其放入缓存中。

三级缓存如何解决循环依赖

当Spring遇到循环依赖问题时,它会按照以下步骤解决:

  1. 创建Bean A :当Spring开始创建Bean A时,它会首先检查第三级缓存(单例工厂缓存)中是否已经有了Bean A的工厂对象。如果没有,就创建一个新的工厂对象并将其放入缓存中。
  2. 填充Bean A的属性 :在创建Bean A的过程中,Spring需要填充它的属性。如果Bean A的属性中有一个对Bean B的引用,那么Spring会开始创建Bean B。
  3. 创建Bean B :同样地,Spring会检查第三级缓存中是否已经有了Bean B的工厂对象。如果没有,就创建一个新的工厂对象并将其放入缓存中。然后,Spring会检查第二级缓存(早期对象缓存)中是否已经有了Bean A的早期对象。如果有,就将这个早期对象注入到Bean B中,从而解决循环依赖问题。
  4. 完成Bean B的初始化 :在Bean B的初始化完成后,它的完全初始化好的对象会被放入第一级缓存(单例Bean池)中。
  5. 完成Bean A的初始化 :回到Bean A的创建过程,当所有属性都填充完毕后,Spring会调用Bean A的初始化方法。完成后,Bean A的完全初始化好的对象也会被放入第一级缓存中。

通过三级缓存机制,Spring有效地解决了循环依赖问题,确保了Bean的正确初始化。这种机制使得Spring在处理复杂依赖关系时更加灵活和健壮。

Spring可以自动解决的循环依赖

  1. 单例Bean 之间的循环依赖,且依赖关系仅发生在Bean的setter方法(或@Autowired注解的字段)上,而不是构造器参数上。

Spring无法自动解决的循环依赖

  1. 构造器注入 造成的循环依赖。如果两个或多个Bean在构造器级别互相依赖(即各自的构造器参数需要对方的Bean实例),Spring无法提前创建出一个不完全初始化的Bean实例放入缓存供另一方使用,因为构造器必须在对象实例化之初就完成依赖注入。这种情况下的循环依赖必须由开发者自行调整设计以避免或改用setter注入等方式解决。
  2. 非单例作用域 (如prototype作用域)的Bean之间的循环依赖。Spring对非单例Bean的管理方式与单例不同,每次请求都会创建新的实例,因此三级缓存机制不适用于解决非单例Bean之间的循环依赖。
  3. Bean的生命周期回调方法 (如@PostConstruct注解的方法)中发生的循环依赖。虽然基本的属性注入可能已经通过三级缓存解决,但如果在这些生命周期回调方法中尝试访问尚未完全初始化的依赖Bean,可能会引发问题。这类问题需要开发者确保在回调方法中使用的依赖已经完成了其自身的初始化。

总结

Spring通过三级缓存机制自动解决了特定类型的循环依赖,即单例Bean通过setter方法注入形成的循环依赖。对于构造器注入、非单例Bean间的循环依赖以及在Bean生命周期回调方法中出现的循环依赖,Spring无法自动解决,需要开发者自行调整设计或代码实现以消除循环依赖,或者采取其他编程策略来妥善处理。


标题:Spring的循环依赖解决策略
作者:barryzpc
地址:https://myblog.zhengpc.com/articles/2024/04/14/1713098770442.html
说明:转载请注明出处
赞赏:如果对你有帮助,可略微支持一下
赞赏码