这么多年终于弄懂了spring循环依赖和三级缓存

首先什么是循环依赖

在spring中我们经常使用@Autowired注解来注入依赖的bean,但是如果出现如下情况,A依赖了B,B又依赖了A我们称之为循环依赖:

java 复制代码
@Component
public class CircleA {
    @Autowired
    private CircleB circleB;

}
java 复制代码
@Component
public class CircleB {
    @Autowired
    private CircleA circleA;
}

循环依赖存在什么问题呢?

我们可以把spring简单理解成一个大map,key是bean的名称,value是bean的实例化对象,

  1. 当我们需要使用一个bean的时候就从这个map里面获取,
  2. 当我们新创建一个bean之后,就往这个map里面放

但是如果发生了循环依赖,实例化A的时候需要依赖B,这个时候就会去实例化B,但是这个时候B依赖了A,因为A还没实例化,所以在map里找不到A的bean,又出实例化A,这样子就进入了一个死循环,如图:

spring 是怎么解决这个问题的呢?

spring 的解决方案是通过缓存来解决的,spring定义了一个三级缓存的机制:

  • 一级缓存(singletonObject):已经实例化的对象,已经分配空间,并且执行完相关初始化方法(例如:init-method、PostProcesser),可以直接使用了
  • 二级缓存(earlySingletonObjects):提前对外暴露的对象,已经分配空间,但是还没有执行初始化方法
  • 三级缓存(singletonFactories):bean工厂,三级缓存中的对象是一个beanFactory,当我们从三级缓存中获取对象的时候,我们要从三级缓存中获取beanFactory之后调用getObject方法获取对外暴露的bean

在spring getBean的时候会先从一级缓存中获取,然后再从二级缓存中获取,最后再尝试从三级缓存获取,以下是spring的DefaultSingletonBeanRegistry#getSingleton方法的源码

java 复制代码
@Nullable
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;
}

最后来一张我觉得画的特好的一张图,该图详细说明了spring解决循环依赖的过程:

哪些对象会提前放入三级缓存中呢?

当我们进入spring 的AbstractAutowireCapableBeanFactory#createBean方法,我们可以看到有一个关于是否提前暴露的判断,符合条件的会被提前放到三级缓存:

java 复制代码
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
      throws BeanCreationException {
    。。。

   // Eagerly cache singletons to be able to resolve circular references
   // even when triggered by lifecycle interfaces like BeanFactoryAware.
   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));
   }
   。。。

   return exposedObject;
}

为什么要三级存储,明明两层的缓存就够了呀?

主要是为了解决AOP 代理bean的循环依赖问题,我们知道被AOP注解的bean,实际在使用的是它的代理类,但是时间创建代理类是在填充完属性之后再实现beanPostProcesser的时候创建的代理类,如果要提前创建代理类,那么就需要打破这个流程因此spring通过一个三级缓存,提前给出一个对象引用,用于依赖注入,后面再按流程进行bean的实例化。

相关推荐
Java爱好狂.2 小时前
Java初学者如何一周速通SpringBoot!
java·spring boot·后端·spring·高并发·架构师·java面试
马良神笔4 小时前
2026 年Rag最近技术前沿总结
后端·python·django
程序猿DD4 小时前
同样10美元,OpenDesign和Command Code的DeepSeek V4.1 Flash额度到底有多少?
后端
高频因子挖掘机5 小时前
A股量化从日线升级到5分钟线:数据量增加后最先暴露哪些工程问题?
后端·github·api
高频因子挖掘机5 小时前
历史行情返回顺序变了,为什么按行号计算指标很危险?
后端·github·api
团子哒哒6 小时前
数组、切片和Map
后端
专注仿真7 小时前
ThinkPHP6 API接口开发实例:从零搭建RESTful风格接口
后端·php·restful
开心猴爷7 小时前
应对iOS审核4.3:可以使用 IpaGuard 来给属性名混淆的技术细节与平衡之道
后端·ios
1m555555557 小时前
RAG 工程实战(五):正确知识已经进入 Prompt,为什么大模型还是会答错?
后端·面试
易小尊7 小时前
我的模型在 65% 以上时准得可怕,在 50% 时像在掷硬币
后端