当前位置: 首页 > news >正文

Spring源码之解决循环依赖 三级缓存

目录

三级缓存核心原理

循环依赖的解决过程

1. Bean A创建过程中提前曝光工厂

2. Bean B创建时发现依赖A,从缓存获取

3. Bean A继续完成初始化

三级缓存的作用总结

二级缓存为何不够解决缓存依赖?

三级缓存如何解决?

为什么不直接在实例化后创建代理?

总结


Spring通过三级缓存机制解决循环依赖问题,这与populateBean()方法密切相关。下面我用简化代码解释这个机制:

三级缓存核心原理

Spring在DefaultSingletonBeanRegistry中维护了三个重要的缓存:

// 一级缓存:存储完全初始化的单例bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);// 二级缓存:存储早期曝光的单例bean(未完全初始化)
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);// 三级缓存:存储单例工厂对象,用于创建早期曝光的bean
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

循环依赖的解决过程

当发生循环依赖时,Spring通过以下步骤解决:

1. Bean A创建过程中提前曝光工厂
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {// 1. 实例化Bean(调用构造函数)BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);// 2. 提前曝光一个ObjectFactory,用于解决循环依赖boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences);if (earlySingletonExposure) {addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));}// 3. 填充属性(可能触发对Bean B的依赖)populateBean(beanName, mbd, instanceWrapper);// 4. 初始化Bean(调用init方法和AOP代理)exposedObject = initializeBean(beanName, exposedObject, mbd);return exposedObject;
}// 将工厂添加到三级缓存
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {synchronized (this.singletonObjects) {if (!this.singletonObjects.containsKey(beanName)) {this.singletonFactories.put(beanName, singletonFactory);this.earlySingletonObjects.remove(beanName);}}
}
2. Bean B创建时发现依赖A,从缓存获取

populateBean()处理B的依赖时:

protected Object resolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) {// 查找依赖的bean(假设这里是A)String dependencyName = descriptor.getDependencyName();// 从三级缓存中获取beanObject bean = getSingleton(dependencyName, false);return bean;
}// 从三级缓存中获取bean的核心方法
protected Object getSingleton(String beanName, boolean allowEarlyReference) {// 1. 先从一级缓存获取Object singletonObject = this.singletonObjects.get(beanName);// 2. 如果一级缓存没有且bean正在创建中if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {// 3. 从二级缓存获取singletonObject = this.earlySingletonObjects.get(beanName);// 4. 二级缓存没有且允许早期引用if (singletonObject == null && allowEarlyReference) {synchronized (this.singletonObjects) {// 5. 从三级缓存获取工厂ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {// 6. 通过工厂创建早期bean实例singletonObject = singletonFactory.getObject();// 7. 放入二级缓存this.earlySingletonObjects.put(beanName, singletonObject);// 8. 从三级缓存移除this.singletonFactories.remove(beanName);}}}}return singletonObject;
}
3. Bean A继续完成初始化

当B完成创建后,A继续完成自己的初始化:

// 完成Bean A的初始化
exposedObject = initializeBean(beanName, exposedObject, mbd);// 将完全初始化的bean放入一级缓存
addSingleton(beanName, exposedObject);protected void addSingleton(String beanName, Object singletonObject) {synchronized (this.singletonObjects) {this.singletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);this.earlySingletonObjects.remove(beanName);}
}

三级缓存的作用总结

  1. 一级缓存(singletonObjects):存储完全初始化的单例bean,可直接使用
  2. 二级缓存(earlySingletonObjects):存储早期曝光的bean(未完成初始化),用于解决循环依赖
  3. 三级缓存(singletonFactories):存储工厂对象,允许在需要时创建早期bean,并支持AOP代理

通过这种机制,Spring允许在bean尚未完全初始化时就将其引用暴露给依赖它的其他bean,从而解决循环依赖问题。注意:只有单例作用域的bean支持循环依赖,原型模式不支持。你的问题非常关键!实际上,二级缓存理论上可以解决循环依赖,但Spring依然使用三级缓存,主要出于AOP代理的考虑。我来详细解释:

二级缓存为何不够解决缓存依赖?

假设只有两级缓存:

  1. singletonObjects(完全初始化的bean)
  2. earlySingletonObjects(早期曝光的bean)

当出现循环依赖时,我们可以在实例化后直接将bean放入earlySingletonObjects。但这样会有一个问题:如果bean需要AOP代理,早期曝光的将是原始对象,而不是代理对象

举个例子:

// Bean A依赖B,Bean B依赖A
class A {@Autowired private B b;@Transactional public void doSomething() {} // 需要AOP代理
}class B {@Autowired private A a;
}

当A和B循环依赖时:

  1. A实例化后放入二级缓存(此时是原始对象)
  2. B创建时从二级缓存获取A的原始对象
  3. B完成创建,注入A的原始对象
  4. A继续初始化,创建代理对象

最终结果:B持有的是A的原始对象,而不是代理对象,导致AOP失效!

三级缓存如何解决?

Spring通过三级缓存引入了ObjectFactory,将AOP代理的创建延迟到真正需要早期引用时:

// 三级缓存:存储工厂对象,用于创建可能需要代理的早期bean
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);// 添加工厂到三级缓存
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));// 获取早期引用的方法(可能创建代理)
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {Object exposedObject = bean;if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {for (BeanPostProcessor bp : getBeanPostProcessors()) {if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;// 如果需要代理,这里会创建代理对象exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);}}}return exposedObject;
}

当B需要A的早期引用时:

  1. 从三级缓存获取ObjectFactory
  2. 通过工厂调用getEarlyBeanReference(),此时才决定是否创建代理
  3. 将结果(可能是代理对象)放入二级缓存

这样一来,B持有的就是A的代理对象,保证了AOP的正确性。

为什么不直接在实例化后创建代理?

你可能会问:为什么不直接在实例化A后就创建代理,然后放入二级缓存?这样不就不需要三级缓存了吗?

原因有两点:

  1. 性能优化:不是所有bean都需要代理,延迟到真正需要时再创建可以避免不必要的代理
  2. 顺序正确性:Spring的后置处理器执行顺序是有规范的。postProcessBeforeInitializationpostProcessAfterInitialization应该在bean初始化阶段执行,而不是实例化阶段。如果提前创建代理,会破坏这个顺序。

总结

二级缓存可以解决普通对象的循环依赖,但无法解决代理对象的循环依赖。三级缓存通过引入ObjectFactory,将代理创建延迟到真正需要早期引用时,既保证了AOP的正确性,又维持了后置处理器的执行顺序。这是Spring在循环依赖和AOP之间找到的精妙平衡点。

相关文章:

  • Python二进制运算:高效操作与实用技巧
  • OpenHarmony外设驱动使用 (二),Camera
  • [ctfshow web入门] web118
  • hysAnalyser 从MPEG-TS导出ES功能说明
  • Leaflet使用SVG创建动态Legend
  • wifiactivity 界面 要在哪里注册,注销广播?onResume() vs onPause() 的本质区别
  • 安装nerdctl和buildkitd脚本命令
  • LLM智能体新纪元:深入解析MCP与A2A协议,赋能智能自动化协作
  • stack和queue简单模拟实现
  • Linux面试题集合(3)
  • 【typenum】 9 与常量泛型桥接(generic_const_mappings.rs)
  • Redis的发布订阅模型是什么,有哪些缺点?
  • GO语言语法---if语句
  • flutter flutter run 运行项目卡在Running Gradle task ‘assembleDebug‘...
  • Python文件与JSON操作全解:从基础到企业级实践
  • 05、基础入门-SpringBoot-HelloWorld
  • 政府数据开放试点企业如何抢占特许经营协议黄金席位
  • acwing5579 增加模数
  • 蓝桥杯11届国B 答疑
  • NY321NY322美光闪存芯片NY323NY336
  • 浙江一教师被指殴打并威胁小学生,教育局通报涉事人被行拘
  • 经济日报金观平:促进信贷资金畅达小微企业
  • 美国新泽西客运公司遭遇罢工:40年来首次,35万人受影响
  • 首次带人形机器人走科技节红毯,傅利叶顾捷:机器人行业没包袱,很多事都能从零开始
  • 秘鲁总统任命前司法部长阿拉纳为新总理
  • 在古老的意大利科莫歌剧院,廖昌永唱响16首中国艺术歌曲