Spring源码分析之循环依赖及解决方案(一)

简介: Spring源码分析之循环依赖及解决方案

往期文章:

  1. Spring源码分析之预启动流程
  2. Spring源码分析之BeanFactory体系结构
  3. Spring源码分析之BeanFactoryPostProcessor调用过程详解
  4. Spring源码分析之Bean的创建过程详解

正文:

首先,我们需要明白什么是循环依赖?简单来说就是A对象创建过程中需要依赖B对象,而B对象创建过程中同样也需要A对象,所以A创建时需要先去把B创建出来,但B创建时又要先把A创建出来…死循环有木有…

循环依赖.png


那么在Spring中,有多少种循环依赖的情况呢?大部分人只知道两个普通的Bean之间的循环依赖,而Spring中其实存在三种对象(普通Bean,工厂Bean,代理对象),他们之间都会存在循环依赖,这里我给列举出来,大致分别以下几种:

  • 普通Bean与普通Bean之间
  • 普通Bean与代理对象之间
  • 代理对象与代理对象之间
  • 普通Bean与工厂Bean之间
  • 工厂Bean与工厂Bean之间
  • 工厂Bean与代理对象之间

那么,在Spring中是如何解决这个问题的呢?

1. 普通Bean与普通Bean

首先,我们先设想一下,如果让我们自己来编码,我们会如何解决这个问题?

栗子

现在我们有两个互相依赖的对象A和B

public class NormalBeanA {
  private NormalBeanB normalBeanB;
  public void setNormalBeanB(NormalBeanB normalBeanB) {
    this.normalBeanB = normalBeanB;
  }
}
public class NormalBeanB {
  private NormalBeanA normalBeanA;
  public void setNormalBeanA(NormalBeanA normalBeanA) {
    this.normalBeanA = normalBeanA;
  }
}

然后我们想要让他们彼此都含有对象

public class Main {
  public static void main(String[] args) {
    // 先创建A对象
    NormalBeanA normalBeanA = new NormalBeanA();
    // 创建B对象
    NormalBeanB normalBeanB = new NormalBeanB();
    // 将A对象的引用赋给B
    normalBeanB.setNormalBeanA(normalBeanA);
    // 再将B赋给A
    normalBeanA.setNormalBeanB(normalBeanB);
  }
}

发现了吗?我们并没有先创建一个完整的A对象,而是先创建了一个空壳对象(Spring中称为早期对象),将这个早期对象A先赋给了B,使得得到了一个完整的B对象,再将这个完整的B对象赋给A,从而解决了这个循环依赖问题,so easy!

那么Spring中是不是也这样做的呢?我们就来看看吧~

Spring中的解决方案

由于上一篇已经分析过Bean的创建过程了,其中的某些部分就不再细讲了

先来到创建Bean的方法

AbstractAutowireCapableBeanFactory#doCreateBean

假设此时在创建A

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args){
  // beanName -> A
  // 实例化A
  BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
  // 是否允许暴露早期对象
  boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                                    isSingletonCurrentlyInCreation(beanName));
  if (earlySingletonExposure) {
    // 将获取早期对象的回调方法放到三级缓存中
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
  }
}

addSingletonFactory

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {
      // 单例缓存池中没有该Bean
      if (!this.singletonObjects.containsKey(beanName)) {
        // 将回调函数放入三级缓存
        this.singletonFactories.put(beanName, singletonFactory);
        this.earlySingletonObjects.remove(beanName);
        this.registeredSingletons.add(beanName);
      }
    }
  }

ObjectFactory是一个函数式接口

在这里,我们发现在创建Bean时,Spring不管三七二十一,直接将一个获取早期对象的回调方法放进了一个三级缓存中,我们再来看一下回调方法的逻辑

getEarlyBeanReference

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
  Object exposedObject = bean;
  // 调用BeanPostProcessor对早期对象进行处理,在Spring的内置处理器中,并无相关的处理逻辑
  // 如果开启了AOP,将引入一个AnnotationAwareAspectJAutoProxyCreator,此时将可能对Bean进行动态代理
  if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
    for (BeanPostProcessor bp : getBeanPostProcessors()) {
      if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
        SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
        exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
      }
    }
  }
  return exposedObject;
}

在这里,如果没有开启AOP,或者该对象不需要动态代理,会直接返回原对象

此时,已经将A的早期对象缓存起来了,接下来在填充属性时会发生什么呢?

相信大家也应该想到了,A对象填充属性时必然发现依赖了B对象,此时就将转头创建B,在创建B时同样会经历以上步骤,此时就该B对象填充属性了,这时,又将要转头创建A,那么,现在会有什么不一样的地方呢?我们看看getBean的逻辑吧

doGetBean

protected <T> T doGetBean(
      String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly){
  // 此时beanName为A
  String beanName = transformedBeanName(name);
  // 尝试从三级缓存中获取bean,这里很关键
  Object sharedInstance = getSingleton(beanName);
}
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
  // 从单例缓存池中获取,此时仍然是取不到的
  Object singletonObject = this.singletonObjects.get(beanName);
  // 获取不到,判断bean是否正在创建,没错,此时A确实正在创建
  if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
    // 由于现在仍然是在同一个线程,基于同步锁的可重入性,此时不会阻塞
    synchronized (this.singletonObjects) {
      // 从早期对象缓存池中获取,这里是没有的
      singletonObject = this.earlySingletonObjects.get(beanName);
      if (singletonObject == null && allowEarlyReference) {
        // 从三级缓存中获取回调函数,此时就获取到了我们在创建A时放入的回调函数
        ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
        if (singletonFactory != null) {
          // 调用回调方法获取早期bean,由于我们现在讨论的是普通对象,所以返回原对象
          singletonObject = singletonFactory.getObject();
          // 将早期对象放到二级缓存,移除三级缓存
          this.earlySingletonObjects.put(beanName, singletonObject);
          this.singletonFactories.remove(beanName);
        }
      }
    }
  }
  // 返回早期对象A
  return singletonObject;
}

震惊!此时我们就拿到了A的早期对象进行返回,所以B得以被填充属性,B创建完毕后,又将返回到A填充属性的过程,A也得以被填充属性,A也创建完毕,这时,A和B都创建好了,循环依赖问题得以收场~

解决过程.png

普通Bean和普通Bean之间的问题就到这里了,不知道小伙伴们有没有晕呢~

2. 普通Bean和代理对象

普通Bean和代理对象之间的循环依赖与两个普通Bean的循环依赖其实大致相同,只不过是多了一次动态代理的过程,我们假设A对象是需要代理的对象,B对象仍然是一个普通对象,然后,我们开始创建A对象。

刚开始创建A的过程与上面的例子是一模一样的,紧接着自然是需要创建B,然后B依赖了A,于是又倒回去创建A,此时,再次走到去缓存池获取的过程。

// 从三级缓存中获取回调函数
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
  // 调用回调方法获取早期bean,此时返回的是一个A的代理对象
  singletonObject = singletonFactory.getObject();
  // 将早期对象放到二级缓存,移除三级缓存
  this.earlySingletonObjects.put(beanName, singletonObject);
  this.singletonFactories.remove(beanName);
}

这时就不太一样了,在singletonFactory.getObject()时,由于此时A是需要代理的对象,在调用回调函数时,就会触发动态代理的过程

AbstractAutoProxyCreator#getEarlyBeanReference

public Object getEarlyBeanReference(Object bean, String beanName) {
  // 生成一个缓存Key
  Object cacheKey = getCacheKey(bean.getClass(), beanName);
  // 放入缓存中,用于在初始化后调用该后置处理器时判断是否进行动态代理过
  this.earlyProxyReferences.put(cacheKey, bean);
  // 将对象进行动态代理
  return wrapIfNecessary(bean, beanName, cacheKey);
}

此时,B在创建时填充的属性就是A的代理对象了,B创建完毕,返回到A的创建过程,但此时的A仍然是一个普通对象,可B引用的A已经是个代理对象了,不知道小伙伴看到这里有没有迷惑呢?

不急,让我们继续往下走,填充完属性自然是需要初始化的,在初始化后,会调用一次后置处理器,我们看看会不会有答案吧

初始化

protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
  //...省略前面的步骤...
  // 调用初始化方法
  invokeInitMethods(beanName, wrappedBean, mbd);
  // 处理初始化后的bean
  wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}

在处理初始化后的bean,又会调用动态代理的后置处理器了

public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
  if (bean != null) {
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    // 判断缓存中是否有该对象,有则说明该对象已被动态代理,跳过
    if (this.earlyProxyReferences.remove(cacheKey) != bean) {
      return wrapIfNecessary(bean, beanName, cacheKey);
    }
  }
  return bean;
}

不知道小伙伴发现没有,earlyProxyReferences这个缓存可不就是我们在填充B的属性,进而从缓存中获取A时放进去的吗?不信您往上翻到getEarlyBeanReference的步骤看看~

所以,此时并未进行任何处理,依旧返回了我们的原对象A,看来这里并没有我们要的答案,那就继续吧~

// 是否允许暴露早期对象
if (earlySingletonExposure) {
  // 从缓存池中获取早期对象
  Object earlySingletonReference = getSingleton(beanName, false);
  if (earlySingletonReference != null) {
    // bean为初始化前的对象,exposedObject为初始化后的对象
    // 判断两对象是否相等,基于上面的分析,这两者是相等的
    if (exposedObject == bean) {
      // 将早期对象赋给exposedObject
      exposedObject = earlySingletonReference;
    }
  }
}

我们来分析一下上面的逻辑,getSingleton从缓存池中获取早期对象返回的是什么呢?

synchronized (this.singletonObjects) {
  // 从早期对象缓存池中获取,此时就拿到了我们填充B属性时放入的A的代理对象
  singletonObject = this.earlySingletonObjects.get(beanName);
  if (singletonObject == null && allowEarlyReference) {
    // 从三级缓存中获取回调函数
    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
    if (singletonFactory != null) {
      // 调用回调方法获取早期bean
      singletonObject = singletonFactory.getObject();
      // 将早期对象放到二级缓存,移除三级缓存
      this.earlySingletonObjects.put(beanName, singletonObject);
      this.singletonFactories.remove(beanName);
    }
  }
}

发现了吗?此时我们就获取到了A的代理对象,然后我们又把这个对象赋给了exposedObject,此时创建对象的流程走完,我们得到的A不就是个代理对象了吗~

此次栗子是先创建需要代理的对象A,假设我们先创建普通对象B会发生什么呢?


目录
相关文章
|
3天前
|
监控 Java 应用服务中间件
Spring Boot整合Tomcat底层源码分析
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置和起步依赖等特性,大大简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是其与Tomcat的整合。
19 1
|
27天前
|
Dubbo Java 应用服务中间件
Spring Cloud Dubbo:微服务通信的高效解决方案
【10月更文挑战第15天】随着信息技术的发展,微服务架构成为企业应用开发的主流。Spring Cloud Dubbo结合了Dubbo的高性能RPC和Spring Cloud的生态系统,提供高效、稳定的微服务通信解决方案。它支持多种通信协议,具备服务注册与发现、负载均衡及容错机制,简化了服务调用的复杂性,使开发者能更专注于业务逻辑的实现。
51 2
|
3天前
|
缓存 架构师 Java
图解 Spring 循环依赖,一文吃透!
Spring 循环依赖如何解决,是大厂面试高频,本文详细解析,建议收藏。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
图解 Spring 循环依赖,一文吃透!
|
18天前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
28 1
|
19天前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
25 1
|
16天前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
20 0
|
1月前
|
缓存 Java Spring
源码解读:Spring如何解决构造器注入的循环依赖?
本文详细探讨了Spring框架中的循环依赖问题,包括构造器注入和字段注入两种情况,并重点分析了构造器注入循环依赖的解决方案。文章通过具体示例展示了循环依赖的错误信息及常见场景,提出了三种解决方法:重构代码、使用字段依赖注入以及使用`@Lazy`注解。其中,`@Lazy`注解通过延迟初始化和动态代理机制有效解决了循环依赖问题。作者建议优先使用`@Lazy`注解,并提供了详细的源码解析和调试截图,帮助读者深入理解其实现机制。
29 1
|
1月前
|
Java Maven Spring
用Spring导致的无法运行Java文件的问题的解决方案
本文提供了解决在IntelliJ IDEA社区版中使用Spring Initializr插件创建Spring项目后,Java文件无法运行的问题的方法,主要是通过加载Maven项目来解决。
69 0
|
2月前
|
缓存 Java 开发工具
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
三级缓存是Spring框架里,一个经典的技术点,它很好地解决了循环依赖的问题,也是很多面试中会被问到的问题,本文从源码入手,详细剖析Spring三级缓存的来龙去脉。
185 24
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
|
存储 缓存 Java
Spring 动态代理时是如何解决循环依赖的?为什么要使用三级缓存?
在研究 『 Spring 是如何解决循环依赖的 』 的时候,了解到 Spring 是借助三级缓存来解决循环依赖的。
472 0