Spring源码解析之循环依赖的实现流程

目录
  • 前言
  • 循环依赖实现流程

前言

上篇文章中我们分析完了Spring中Bean的实例化过程,但是没有对循环依赖的问题进行分析,这篇文章中我们来看一下spring是如何解决循环依赖的实现。

之前在讲spring的过程中,我们提到了一个spring的单例池singletonObjects,用于存放创建好的bean,也提到过这个Map也可以说是狭义上的spring容器。

private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);

其实spring在缓存bean的过程中并不是只有这一个Map,我们看一下DefaultSingletonBeanRegistry这个类,在其中其实存在3个Map,这也就是经常提到的spring三级缓存。

/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
/** Cache of early singleton objects: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);

从上到下分别为一到三级缓存,这里先对三级缓存有一个初步的认识,后面使用到的时候我们再详细分析。

循环依赖实现流程

下面开始分析spring循环依赖的注入实现过程。先写两个bean,在它们中分别注入了对方:

@Component
public class ServiceA {
  @Autowired
  ServiceB serviceB;  

  public ServiceB getServiceB() {
    System.out.println("get ServiceB");
    return serviceB;
  }
}
@Component
public class ServiceB {
  @Autowired
  ServiceA serviceA;  

  public ServiceA getServiceA() {
    return serviceA;
  }
}

进行测试,分别调用它们的get方法,能够正常获得bean,说明循环依赖是可以实现的:

com.hydra.service.ServiceB@58fdd99
com.hydra.service.ServiceA@6b1274d2

首先,回顾一下上篇文章中讲过的bean实例化的流程。下面的内容较多依赖于spring的bean实例化源码,如果不熟悉建议花点时间阅读一下上篇文章。

AbstractAutowireCapableBeanFactorydoCreateBean方法中,调用createBeanInstance方法创建一个原生对象,之后调用populateBean方法执行属性的填充,最后调用各种回调方法和后置处理器。

但是在执行populateBean方法前,上篇文章中省略了一些涉及到循环依赖的内容,看一下下面这段代码:

上面的代码先进行判断:如果当前创建的是单例bean,并且允许循环依赖,并且处于创建过程中,那么执行下面的addSingletonFactory方法。

主要工作为将lambda表达式代表的ObjectFactory,放入三级缓存的Map中。注意这里只是一个存放的操作,并没有实际执行lambda表达式中的内容,具体调用过程是在后面调用ObjectFactory的getObject方法时调用。这个方法执行完成后,三级缓存中存放了一条serviceA的数据,二级缓存仍然为空。

回到正常调用流程,生成原生对象后,调用populateBean方法进行属性的赋值也就是依赖注入,具体是通过执行AutowiredAnnotationBeanPostProcessor这一后置处理器的postProcessPropertyValues方法。

在这一过程中,serviceA会找到它依赖的serviceB这一属性,当发现依赖后,会调用DefaultListableBeanFactorydoResolveDependency方法,之后执行resolveCandidate方法,在该方法中,尝试使用beanFactory获取到serviceB的bean实例。

public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory)
      throws BeansException {
    return beanFactory.getBean(beanName);
  }

这时和之前没有循环依赖时的情况就会有些不一样了,因为现在serviceB还没有被创建出来,所以通过beanFactory是无法直接获取的。因此当在doGetBean方法中调用getSingleton方法会返回一个null值:

因此,继续使用与之前相同的创建bean的流程,实例化serviceB的bean对象。当serviceB的原生对象被实例化完成后,同样可以看到它依赖的serviceA还没有被赋值:

创建完serviceB的原生对象后,同样执行addSingletonFactory方法,将serviceB放入三级缓存中,执行完成后,三级缓存中就已经存在了两个bean的缓存:

向下执行,serviceB会调用populateBean方法进行属性填充。和之前serviceA依赖serviceB相同的调用链,执行到resolveCandidate方法,尝试使用beanFactorygetBean去获取serviceA

向下执行,调用getSingleton方法尝试直接获取serviceA,此时三级缓存singletonFactories中我们之前已经存进去了一个key为serviceAbeanName,value为lambda表达式,这时可以直接获取到。

在执行singletonFactorygetObject方法时才去真正执行lambda表达式中的方法,实际执行的是getEarlyBeanReference方法:

在遍历后置处理器后,获取到serviceA的执行过后置处理器后的对象,执行:

this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);

这里将serviceA放入二级缓存earlySingletonObjects,并从三级缓存singletonFactories中移除。在这一步执行完后,三级缓存中的serviceA就没有了。

当我们从缓存中获取了serviceA的bean后,就不会再调用createBean去重复创建新的bean了。之后,顺调用链返回serviceB调用的doResolveDependency方法:

serviceB就成功获取到了它的依赖的serviceA属性的bean对象,回到inject方法,使用反射给serviceA赋值成功。

回到doCreateBean的方法,可以看到serviceBserviceA属性已经被注入了,但是serviceA中的serviceB属性还是null。说明serviceB的依赖注入已经完成,而serviceA的依赖注入还没做完。

现在我们梳理一下运行到这里的流程:

1、在serviceA填充属性过程中发现依赖了serviceB,通过beanFactorygetBean方法,尝试获取serviceB

2、serviceB不存在,执行了一遍serviceB的创建流程,填充属性时发现serviceA已经存在于三级缓存,直接注入给serviceB

可以看到,在创建serviceA的过程中发现依赖的serviceB不存在,转而去创建了serviceB,而创建serviceA的流程并没有执行完,因此在创建完serviceB后再顺调用链返回,直到doResolveDependency方法:

可以看到,需要依赖的serviceB已经被创建并返回成功,返回到inject方法,同样通过反射给serviceB赋值:

返回doCreateBean方法,可以看到serviceAserviceB之间的循环依赖已经完成了:

这样,一个最简单的循环依赖流程就结束了。有的小伙伴可能会提出疑问,这样的话,我只需要添加一个缓存存放原生对象就够了啊,为什么还需要二级缓存和三级缓存两层结构呢?这个问题,我们放在下一篇具体讨论,看看循环依赖的具体实现时怎样的。

到此这篇关于Spring源码解析之循环依赖的实现流程的文章就介绍到这了,更多相关Spring循环依赖内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 深度解析SpringBoot中@Async引起的循环依赖

    目录 事故时间线 猜想 什么是循环依赖 什么是@Async 啊,昨晚发版又出现了让有头大的循环依赖问题,按理说Spring会为我们解决循环依赖,但是为什么还会出现这个问题呢?为什么在本地.UAT以及PRE环境都没有出现这个问题,但是到了PROD环境就出现了这个问题呢?本文将从事故时间线.及时止损.复盘分析等几个方面为大家带来详细的分析,干货满满! 事故时间线 本着"先止损.后复盘分析"的原则,我们来看一下这次发版事故的时间线. 2021年11月16日晚23点00分00秒开始发版,此时集

  • 教你Spring如何使用三级缓存解决循环依赖

    一级缓存存放实例化对象 .二级缓存存放已经在内存空间创建好但是还没有给属性赋值的对象.三级缓存存放对象工厂,用来创建提前暴露到bean的对象. @Service public class TestService1 { @Autowired private TestService2 testService2; public void test1() { } } @Service public class TestService2 { @Autowired private TestService1

  • Java中的Spring 如何处理循环依赖

    目录 前言 什么是循环依赖 构造器循环依赖 Setter循环依赖 构造器循环依赖处理 那么Spring到底是如何做的呢? DefaultSingletonBeanRegistry#getSingleton AbstractAutowireCapableBeanFactory#autowireConstructor setter循环依赖处理 AbstractAutowireCapableBeanFactory#doCreateBean prototype模式的循环依赖 总结 前言 Spring如何

  • SpringBoot2.6.x默认禁用循环依赖后的问题解决

    目录 一.序言 二.问题复原 1.代码说明 2.错误示例 三.问题解决 1.粗暴解决 2.优雅解决 四.小结 一.序言 SpringBoot 2.6.x不推荐使用循环依赖,这是一个好消息,SpringBoot从底层逐渐引导开发者书写规范的代码,同时也是个忧伤的消息,循环依赖的应用场景实在是太广泛了. 如果从低版本升级到2.6.x,那么很大概率遇到的第一个问题便是循环依赖问题. 二.问题复原 1.代码说明 下面风格的代码比较普遍:两个类都有调用对方方法的需求,因此很容易写成循环引用. @Servi

  • 关于SpringBoot禁止循环依赖解说

    前言: Spring的Bean管理,一直是整个体系中津津乐道的东西.尤其是Bean的循环依赖,更是很多面试官最喜欢考察的2B知识点之一. 但事实上,项目中存在Bean的循环依赖,是代码质量低下的表现.多数人寄希望于框架层来给擦屁股,造成了整个代码的设计越来越糟,最后用一些奇技淫巧来填补犯下的错误. 还好,SpringBoot终于受不了这种滥用,默认把循环依赖给禁用了! 从2.6版本开始,如果你的项目里还存在循环依赖,SpringBoot将拒绝启动! 验证代码小片段: 为了验证这个功能,我们只需要

  • springboot bean循环依赖实现以及源码分析

    前言 本文基于springboot版本2.5.1 <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.1</version> <relativePath/> <!-- lookup parent from repositor

  • Spring源码解析之循环依赖的实现流程

    目录 前言 循环依赖实现流程 前言 上篇文章中我们分析完了Spring中Bean的实例化过程,但是没有对循环依赖的问题进行分析,这篇文章中我们来看一下spring是如何解决循环依赖的实现. 之前在讲spring的过程中,我们提到了一个spring的单例池singletonObjects,用于存放创建好的bean,也提到过这个Map也可以说是狭义上的spring容器. private final Map<String, Object> singletonObjects = new Concurr

  • Spring源码解析后置处理器梳理总结

    目录 前言 1.InstantiationAwareBeanPostProcessor的postProcessBeforeInstantiation()方法 2.SmartInstantiationAwareBeanPostProcessor的determineCandidateConstructors()方法 3.MergedBeanDefinitionPostProcessor的postProcessMergedBeanDefinition()方法 4.SmartInstantiationA

  • Spring源码解析 Bean的实例化

    目录 前言 准备工作 实例化bean 1.AbstractBeanFactory 的 doGetBean方法 2.AbstractAutowireCapableBeanFactory 的 createBean方法 3.AbstractAutowireCapableBeanFactory 的 doCreateBean方法 4.AbstractAutowireCapableBeanFactory 的 createBeanInstance方法 5.AbstractAutowireCapableBean

  • Spring源码解析之Configuration

    一.@Configuration 1.1 未加@Configuration <!--logback-test.xml,配置不打印日志--> <?xml version="1.0" encoding="UTF-8"?> <configuration> <include resource="org/springframework/boot/logging/logback/base.xml" /> <

  • Spring源码解析之BeanPostProcessor知识总结

    一.简介 BeanPostProcessor是Spring IOC容器给我们提供的一个扩展接口. 实例化Bean做前置处理.后置处理 二.接口定义 @Component public class BeanPost implements BeanPostProcessor { /** * 在每个bean创建之后的初始化方法之前调用 * @param bean 当前实例化的bean * @param beanName bean的名称 * @return 返回实例化的bean或者可以对对象进行再封装返

  • Spring源码解析 Bean属性填充

    目录 前言 属性填充 执行回调方法及后置处理器 前言 在上一篇文章中,我们分析了Spring中Bean的实例化过程,在结尾我们知道了虽然bean的实例化完成了,但是其中的属性还没有被注入,今天我们就接着来分析属性是如何被注入的. 属性填充 实例化完成后,回到上面第3条的doCreateBean方法中,看一下用BeanWrapper产生的原生对象,里面dao这个属性还是null值. 回归一下之前的代码,接下来要调用populateBean方法进行属性的填充: Object exposedObjec

  • Spring源码解析之事务传播特性

    一.使用方式 可以采用Transactional,配置propagation即可. 打开org.springframework.transaction.annotation.Transactional可见默认传播特性是REQUIRED. /** * The transaction propagation type. * <p>Defaults to {@link Propagation#REQUIRED}. * @see org.springframework.transaction.inte

  • Spring源码解析之推断构造方法

    Spring推断构造方法 贴个测试代码直接开干,这只是个样例,其他情况自行分析 @Component public class OrderService { public OrderService() { System.out.println("无参构造方法"); } @Autowired(required = false) public OrderService(UserService userService) { System.out.println("一个参数的构造方法

  • Spring源码解析之编程式事务

    一.前言 在Spring中,事务有两种实现方式: 编程式事务管理: 编程式事务管理使用TransactionTemplate可实现更细粒度的事务控制.声明式事务管理: 基于Spring AOP实现.其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务. 声明式事务管理不需要入侵代码,通过@Transactional就可以进行事务操作,更快捷而且简单(尤其是配合spring boot自动配置,可以说是精简至极!),且大部分业务都可

  • 从Spring源码解析事务失效的原因

    目录 一.前言 二.方法不是 public 的 三.内部方法间调用导致事务失效 四.异常类型是否配置正确 五.异常被catch住 一.前言 1.Bean是否是代理对象 2.入口函数是否是public的 3.数据库是否支持事务(Mysql的Mvlsam不支持事务),行锁才支持事务 4.切点是否配置正确 5.内部方法间调用导致事务失效 因为this不是代理对象,可以配置 expose-proxy="true",就可以通过AopContext.currentProxy()获取到当前类的代理对

随机推荐