Bootstrap

每天一道Java面试题系列之--Spring如何解决循环依赖问题

面试题:Spring如何解决循环依赖问题?

问题背景:

在Spring框架中,循环依赖通常发生在单例(Singleton)作用域的bean之间。当两个或多个bean在它们的构造函数中相互引用时,Spring容器在创建这些bean时会遇到问题,因为每个bean的构造都需要其他bean已经完全初始化。

Spring的解决方案:

Spring通过以下几种方式解决单例作用域bean的循环依赖问题:

  1. 三级缓存机制

    • 一级缓存(Singleton Objects):存放已经经过完整生命周期处理的bean,包括初始化。
    • 二级缓存(Early Singleton Objects):存放原始的bean对象,即已经实例化,但尚未初始化。
    • 三级缓存(Singleton Factories):存放bean工厂对象,用于存放一个bean创建的逻辑。
  2. 延迟依赖注入

    • 当Spring容器创建bean时,首先实例化bean并放入三级缓存中。
    • 然后,Spring容器尝试注入bean的依赖项,如果依赖项尚未创建,则Spring容器会暂时放下当前bean的创建过程,先去创建依赖项。
    • 当依赖项创建并初始化完成后,Spring容器会回到原始bean的创建过程中,从三级缓存中取出bean工厂对象,使用它来创建bean实例,并将其放入二级缓存中。
    • 最后,当所有依赖项都注入完成后,Spring容器将bean从二级缓存移动到一级缓存,并触发bean的初始化方法。
  3. 使用@Lazy注解

    • 在某些情况下,可以通过在依赖注入时使用@Lazy注解来避免循环依赖。@Lazy注解会延迟依赖bean的加载,直到它被实际使用。
  4. 构造器注入

    • 循环依赖通常发生在构造器注入中,因为Spring需要在构造函数中立即解决所有依赖关系。通过使用setter注入或字段注入,可以减少循环依赖的可能性。
示例代码:
@Service
public class ServiceA {

    private final ServiceB serviceB;

    @Autowired
    public ServiceA(ServiceB serviceB) {
        this.serviceB = serviceB;
    }

    // ...
}

@Service
public class ServiceB {

    private final ServiceA serviceA;

    @Autowired
    public ServiceB(ServiceA serviceA) {
        this.serviceA = serviceA;
    }

    // ...
}

在这个例子中,ServiceAServiceB相互依赖,Spring通过三级缓存机制解决这个问题。

结论:

Spring框架通过其强大的依赖注入机制和三级缓存策略,有效地解决了单例作用域bean的循环依赖问题。开发者应了解这些机制,并在设计应用时考虑如何避免循环依赖。

;