Skip to content
章节导航

Bean 的三级缓存

Spring 的三级缓存是解决循环依赖的核心机制。

常见的 bean 依赖场景

循环依赖问题

java
@Component
public class CycleServiceA {
    @Autowired
    private CycleServiceB serviceB;  // A依赖B
}

@Component
public class CycleServiceB {
    @Autowired
    private CycleServiceA serviceA;  // B依赖A,形成循环依赖
}

// ❌ 问题:先创建 A 需要 B,先创建 B 需要 A,死锁!

以上情况为属性注入循环依赖, IOC 容器可能会解决掉,在配置文件中配置如下:

yaml
spring:
  main:
    allow-circular-references: true

Spring 内部的标志位,默认允许。可以通过 AbstractAutowireCapableBeanFactory 类中的 allowCircularReferences 查看。

也可以明确禁止:

java
@SpringBootApplication
public class Application {
    
    public static void main(String[] args) {
        // 方式1:禁用循环依赖(推荐用于生产环境)
        SpringApplication app = new SpringApplication(Application.class);
        app.setAllowCircularReferences(false);  // 明确禁止
        app.run(args);
        
        // 应用启动时会检查循环依赖,发现则直接报错
        // 错误信息:The dependencies of some of the beans in the application context form a cycle
    }
}

注入形式可以还可以通过构造函数、setter 注入。通过构造器函数进行依赖,Spring IOC 是无法解决循环注入依赖的。

三级缓存

java
public class DefaultSingletonBeanRegistry {
    
    // 一级缓存:完全初始化好的单例 Bean,功能完备的 Bean,应用程序需要使用的时候,需要从这个 Map 里面调用
    // Key: beanName, Value: 完整的Bean实例
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
    
    // 二级缓存:提前暴露的 Bean(半成品),实例化之后,没有完成属性注入的 Bean。
    // 存在的意义是提前暴露 Bean,让其他的 Bean 知道有这么一个 Bean 存在于 Spring IOC 中,已经可以使用了。
    // Key: beanName, Value: 早期的Bean引用
    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
    
    // 三级缓存:ObjectFactory工厂
    // 创建 Bean 早期引用(或代理对象)的工厂,生成原始对象,对 AOP 进行操作后的代理对象
    // 代理对象:在不修改原始对象的情况下,控制对原始对象的访问
    // Key: beanName, Value: 创建Bean的工厂
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
    
    // 正在创建中的Bean名称(Set集合)
    private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));
}

解决循环依赖

  • 1、在创建 Bean A 的时候,将 Bean A 的原始对象,存放在缓存中。这里只是将 A new 出来了,并没有将它注入所需要的依赖。
  • 2、Spring IOC 实现依赖注入的时候,发现需要依赖 B
  • 3、创建 B 对象,实例化
  • 4、将创建 B 实例化之后的原始对象放在缓存里
  • 5、在对 B 进行依赖注入时,发现需要 A
  • 6、从缓存中取出已经被实力化的 A
  • 7、对 B 完成依赖注入
  • 8、完成 A 的依赖注入

二级缓存是否足够了

A 和 B 互相依赖,通过缓存的方式解决了循环依赖的问题,没有使用到第三级缓存的。

第三级缓存 singletonFactories 是为了处理 Spring AOP 的中的代理对象。

没有使用 AOP 的操作, B 和 A 完成创建后,注入的最终对象是同一个对象, 如果并 A 方法中有 AOP 的操作,A 的原始对象的复值给 B 时,A 会进行 AOP 操作,产生一个代理对象

怎么解决工程中存在的循环依赖

  • 1、做好设计和规划,尽量避免多个Bean的功能之间存在交叉,遵循职责独立
  • 2、使用 Abstract Bean,公用的功能定义在其中
  • 3、剥离出『中间 Bean』,其他 Bean 对其依赖注入