day07-Spring循环依赖

day07-Spring循环依赖

前言:

项目启动时候,可能会存在A创建依赖B,B创建依赖A,这样就出现了循环依赖。

摘要:

Spring通过三级缓存机制解决循环依赖问题。一级缓存存储完整初始化的单例Bean,二级缓存存储半成品Bean,三级缓存存储ObjectFactory用于生成早期引用。当检测到循环依赖时,Spring会通过三级缓存获取早期引用并存入二级缓存,避免重复创建。该机制还支持AOP代理,确保在循环依赖时也能正确创建代理对象。核心流程包括实例化后提前暴露ObjectFactory、三级缓存的添加与查询逻辑,以及早期引用的处理,从而保证Bean创建过程的一致性和正确性。

流程图:

1、三级缓存的定义

c 复制代码
// 一级缓存:存储完全初始化好的单例bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

// 二级缓存:存储提前暴露(已经初始化,但是没有实例化)
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

// 三级缓存:存储ObjectFactory,用于生成早期Bean的引用
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

1、singletonObjects :缓存经过了完整生命周期的bean

2、earlySingletonObjects:缓存未经过完整生命周期的bean,

2.1 如果某个bean出现了循环依赖,就会提前把暂时未经过完整生命周期的bean放入earlySingletonObjects中,

2.2 这个bean如果经过AOP那么就会把代理对象放入earlySingletonObjects中,否则就把原始对象放入earlySingletonObjects中。

3、singletonFactories:缓存一个ObjectFactory,也是 Lambda表达式。每个Bean的生成过程中,经过实例化得到一个原始对象后,都会提前暴露一个Lambda表达式,并保存到三级缓存中。

3.1 三级缓存可能用到,也可能用不到。

3.2 如果当前Bean没有出现循环依赖,那么这个Lambda用不到,会正常执行完生成完整的Bean放入 singletonObjects中,

3.3 如果bean在依赖注入时出现了循环依赖,从三级缓存拿到一个对象,放入二级缓存中,并根据beanName从三级缓存中删除。

4、earlyProxyReferences :主要用来记录原始对象是否进行AOP。

2、三级缓存的作用

缓存级别 名称 存储内容 作用
一级缓存 singletonObjects 完全初始化好的Bean 提供最终单例bean
二级缓存 earlySingletonObjects 半成品 避免重复创建早期引用
三级缓存 singletonFactories ObjectFactory 创建早期Bean,支持AOP代理

3、核心代码讲解

3.1 实例化后提前暴露,在Bean的实例化后,属性填充前

1)Spring会判断该Bean单例允许循环引用,将会创建一个返回该Bean的当前的原始对象 ObjectFactory 存入三级缓存中。

2)处理循环依赖,当一个Bean 在填充属性发现依赖另一个 Bean的时候,这时会调用getEarlyBeanReference() 方法,遍历所有的SmartInstantiationAwareBeanPostProcessor ,给后处理器一个机会,返回早期引用。

c 复制代码
// org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java:596
// 单例&&是否是当前正在创建的bean
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
       isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
    if (logger.isTraceEnabled()) {
       logger.trace("Eagerly caching bean '" + beanName +
             "' to allow for resolving potential circular references");
    }
    // 主要是:添加到三级缓存中,并删除二级缓存
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

3.2 三级缓存添加的逻辑

synchronized (this.singletonObjects) 先加入锁原因,是保证多线程的缓存操作下的一致性,防止同一个Bean被重复创建

c 复制代码
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
     synchronized (this.singletonObjects) {
       // 一级缓存不存在情况下
       if (!this.singletonObjects.containsKey(beanName)) {
           // 先添加三级缓存,再根据beanName删除二级缓存中的半成品bean
          this.singletonFactories.put(beanName, singletonFactory);
          this.earlySingletonObjects.remove(beanName);
          this.registeredSingletons.add(beanName);
       }
    }
}

3.3 获取单例bean的缓存查询

c 复制代码
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 1.检查一级缓存,如果有直接返回
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        // 2.从二级缓存中获取
       singletonObject = this.earlySingletonObjects.get(beanName);
       if (singletonObject == null && allowEarlyReference) {
          synchronized (this.singletonObjects) {
             // 双重检查
             singletonObject = this.singletonObjects.get(beanName);
             if (singletonObject == null) {
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null) {
                    // 3.从三级缓存获取,放入二级缓存, 并删除三级缓存
                   ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                   if (singletonFactory != null) {
                      singletonObject = singletonFactory.getObject();
                      this.earlySingletonObjects.put(beanName, singletonObject);
                      this.singletonFactories.remove(beanName);
                   }
                }
             }
          }
       }
    }
    return singletonObject;
}

3.4 早期引用

// org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#getEarlyBeanReference

c 复制代码
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
       for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
           // 获取早期引用 
           exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
       }
    }
    return exposedObject;
}

3.5 AOP代理创建

org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#getEarlyBeanReference

  • 时机:
  • AOP 代理器(AbstractAutoProxyCreator) 在getEarlyBeanReference方法就会工作 在循环依赖发生时就提前创建代理对象,而不是等待初始化之后。
  • 保证一致性:
  • 调用 getEarlyBeanReference() 方法内部会先记录该Bean earlyProxyReferences,postProcessAfterInitialization 方法里会判断该Bean是否已经提前代理过了,如果代理过则不再重复处理,确保整个容器中对于同一个Bean,循环依赖时拿到和最终持有的都是同一个代理对象。【org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization】
c 复制代码
// org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization
// 方法里会判断该Bean是否已经提前代理过了,如果代理过则不再重复处理,确保整个容器中对于同一个Bean,循环依赖时拿到和最终持有的都是同一个代理对象
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
       Object cacheKey = getCacheKey(bean.getClass(), beanName);
       if (this.earlyProxyReferences.remove(cacheKey) != bean) {
          return wrapIfNecessary(bean, beanName, cacheKey);
       }
    }
    return bean;
}


@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    // 记录原始bean,避免重复代理
    this.earlyProxyReferences.put(cacheKey, bean);
    // 如果这个bean经过AOP,那么把这个代理对象放入 earlySingletonObjects,否则把原始对象放入里面
    return wrapIfNecessary(bean, beanName, cacheKey);
}

4、三级缓存的设计思想以及常见面试题

问题一:二级缓存能否去掉

不能,二级缓存作用

  • 避免重复执行ObjectFactory.getObject()(性能考虑)
  • 保证同一个 Bean在循环依赖中返回相同的早期引用。

问题二:添加三级缓存bean的引用和获取singleton bean 添加 synchronized的作用是什么?

c 复制代码
synchronized (this.singletonObjects) {
    // 1.保证多线程环境下,缓存操作的一致性
    // 2.防止同一个bean被重复创建
}

位置如下:

获取bean的位置

c 复制代码
// org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // Quick check for existing instance without full singleton lock
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
       singletonObject = this.earlySingletonObjects.get(beanName);
       if (singletonObject == null && allowEarlyReference) {
          synchronized (this.singletonObjects) {
             // Consistent creation of early reference within full singleton lock
             singletonObject = this.singletonObjects.get(beanName);
             if (singletonObject == null) {
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null) {
                   ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                   if (singletonFactory != null) {
                      singletonObject = singletonFactory.getObject();
                      this.earlySingletonObjects.put(beanName, singletonObject);
                      this.singletonFactories.remove(beanName);
                   }
                }
             }
          }
       }
    }
    return singletonObject;
}

添加三级缓存的地方:

c 复制代码
// org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingletonFactory
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
       if (!this.singletonObjects.containsKey(beanName)) {
          this.singletonFactories.put(beanName, singletonFactory);
          this.earlySingletonObjects.remove(beanName);
          this.registeredSingletons.add(beanName);
       }
    }
}

问题三:在使用Spring如果避免多级缓存带来的复杂性

通过清晰的缓存升级策略

  • 创建中:三级缓存
  • 早期引用:二级缓存
  • 完整体:一级缓存
  • 状态时不可逆,只能向上一季流动。

问题四:为什么必须是三级缓存?

  • 三级缓存:singletonFactories 核心是(ObjectFactory)的能力,封装了复杂的后处理器(如getEarlyBeanReference()) ,可以按需生成,可能被包装过的对象(如代理)。这是 一个"懒加载"过程,只在发生循环依赖时才调用。
  • 二级缓存:earlySingletonObjects,半成品暂存区,一旦通过通过缓存的工厂生产出对象, 就放入二级缓存,下次需要时直接获取,避免重复执行工厂逻辑。
  • 如果只是二级缓存:
  • 那么就需要Bean实例化后立即执行所有可能的AOP代理后处理逻辑来创建对象,这样违反了Bean生命周期的设计原则,而且对于没有循环依赖的Bean,这种"提前代理"是不必要的开销。

喜欢我的文章记得点个在看,或者点赞,持续更新中ing...

相关推荐
shuair5 小时前
redis缓存预热、缓存击穿、缓存穿透、缓存雪崩
redis·spring·缓存
计算机程序设计小李同学5 小时前
基于 Spring Boot + Vue 的龙虾专营店管理系统的设计与实现
java·spring boot·后端·spring·vue
Charlie_lll7 小时前
力扣解题-[3379]转换数组
数据结构·后端·算法·leetcode
qq_12498707537 小时前
基于Java Web的城市花园小区维修管理系统的设计与实现(源码+论文+部署+安装)
java·开发语言·前端·spring boot·spring·毕业设计·计算机毕业设计
VX:Fegn08957 小时前
计算机毕业设计|基于springboot + vue云租车平台系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
Chasmれ7 小时前
Spring Boot 1.x(基于Spring 4)中使用Java 8实现Token
java·spring boot·spring
汤姆yu7 小时前
2026基于springboot的在线招聘系统
java·spring boot·后端
计算机学姐8 小时前
基于SpringBoot的校园社团管理系统
java·vue.js·spring boot·后端·spring·信息可视化·推荐算法
落霞的思绪8 小时前
Spring AI Alibaba 集成 Redis 向量数据库实现 RAG 与记忆功能
java·spring·rag·springai
hssfscv8 小时前
Javaweb学习笔记——后端实战8 springboot原理
笔记·后端·学习