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

相关推荐
小蒜学长13 分钟前
springboot餐厅信息管理系统设计(代码+数据库+LW)
java·数据库·spring boot·后端
Chh4322436 分钟前
React 新版
后端
Miracle6581 小时前
【征文计划】Rokid CXR-M SDK全解析:从设备连接到语音交互的AR协同开发指南
后端
合作小小程序员小小店1 小时前
web开发,学院培养计划系统,基于Python,FlaskWeb,Mysql数据库
后端·python·mysql·django·web app
笃行3502 小时前
基于Rokid CXR-S SDK的智能AR翻译助手技术拆解与实现指南
后端
文心快码BaiduComate2 小时前
代码·创想·未来——百度文心快码创意探索Meetup来啦
前端·后端·程序员
渣哥2 小时前
面试官最爱刁难:Spring 框架里到底用了多少经典设计模式?
javascript·后端·面试
疯狂的程序猴2 小时前
iOS混淆实战全解析,从源码混淆到IPA文件加密,打造苹果应用反编译防护体系
后端
开心就好20252 小时前
iOS 26 文件管理实战,多工具组合下的 App 数据访问与系统日志调试方案
后端
乘风破浪酱524362 小时前
PO、DTO、VO的区别与应用场景详解
后端