这么多年终于弄懂了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的实例化。

相关推荐
llz_1121 小时前
web-第二次课后作业
前端·后端·web
红尘散仙7 小时前
我把终端小说阅读器接上了 AI Agent:TRNovel 现在能用 skill 生成书源了
人工智能·后端·rust
卷毛的技术笔记8 小时前
告别硬编码!Spring AI Alibaba 实现 AI Agent 智能工具调用(Tool Calling)
java·人工智能·后端·python·spring·ai编程
会编程的土豆9 小时前
Go 语言反射(Reflection)详解
开发语言·后端·golang
喵个咪9 小时前
GoWind Toolkit Go后端代码生成 完整全流程实战
后端·go·orm
basketball6169 小时前
Go 语言从入门到进阶:4. 数组和MAP使用方法总结
开发语言·后端·golang
qq_2518364579 小时前
SpringBoot+Vue 共享电池柜管理系统 完整实现 前后端分离项目实战 完整代码
vue.js·spring boot·后端
zhangxingchao10 小时前
AI 大模型核心六:量化、Workflow 与 Agent、多轮 RAG
前端·人工智能·后端
IT_陈寒11 小时前
Vite打包时遇到的坑,原来问题出在这里
前端·人工智能·后端
ayqy贾杰12 小时前
基层管理的三板斧,在AI时代行不通了
前端·后端·团队管理