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...

相关推荐
Java天梯之路2 小时前
Spring Boot 钩子全集实战(六):SpringApplicationRunListener.contextPrepared()详解
java·spring boot·后端
浮尘笔记2 小时前
Go语言上下文:context.Context类型详解
开发语言·后端·golang
康小庄2 小时前
通过NGINX实现将小程序HTTPS请求转为内部HTTP请求
java·spring boot·nginx·spring·http·小程序
Swift社区2 小时前
Date / LocalDateTime 转换错误,一次踩坑后的完整复盘
java·spring boot·spring
古城小栈2 小时前
Rust 模式匹配 大合集
开发语言·后端·rust
brave_zhao2 小时前
关闭 SpringBoot+javaFX混搭程序的最佳实践
spring boot·后端·sql
a努力。2 小时前
得物Java面试被问:B+树的分裂合并和范围查询优化
java·开发语言·后端·b树·算法·面试·职场和发展
霖雨2 小时前
备份 SQL Server 到 Azure Storage
后端·python·microsoft·flask·azure
IT_陈寒3 小时前
5 个现代 JavaScript 特性让你彻底告别老旧写法,编码效率提升 50%
前端·人工智能·后端