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

相关推荐
Asthenia04126 小时前
浏览器缓存机制深度解析:电商场景下的性能优化实践
后端
databook7 小时前
『Python底层原理』--Python对象系统探秘
后端·python
超爱吃士力架8 小时前
MySQL 中的回表是什么?
java·后端·面试
追逐时光者9 小时前
Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
后端·.net
苏三说技术9 小时前
10亿数据,如何迁移?
后端
bobz9659 小时前
openvpn 显示已经建立,但是 ping 不通
后端
customer0810 小时前
【开源免费】基于SpringBoot+Vue.JS个人博客系统(JAVA毕业设计)
java·vue.js·spring boot·后端·开源
qq_4592384910 小时前
SpringBoot整合Redis和Redision锁
spring boot·redis·后端
灰色人生qwer10 小时前
SpringBoot 项目配置日志输出
java·spring boot·后端
阿华的代码王国11 小时前
【从0做项目】Java搜索引擎(6)& 正则表达式鲨疯了&优化正文解析
java·后端·搜索引擎·正则表达式·java项目·从0到1做项目