Spring 源码学习(五) 循环依赖(二)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 还记得上一篇笔记,在 bean 加载流程,在创建过程中,出现了依赖循环的监测,如果出现了这个循环依赖,而没有解决的话,代码中将会报错,然后 Spring 容器初始化失败。 由于感觉循环依赖是个比较独立的知识点,所以我将它的分析单独写一篇笔记,来看下什么是循环依赖和如何解决它。

结合关键代码梳理流程

创建原始 bean

  1. BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
  2. // 原始 bean
  3. finalObject bean = instanceWrapper.getWrappedInstance();

在这一步中,创建的是原始 bean,因为还没到最后一步属性解析,所以这个类里面没有属性值,可以将它想象成 newClassA,同时没有构造函数等赋值的操作,这个原始 bean 信息将会在下一步使用到。


addSingleFactory

  1. // 注释 5.2 解决循环依赖 第二个参数是回调接口,实现的功能是将切面动态织入 bean
  2. addSingletonFactory(beanName,()-> getEarlyBeanReference(beanName, mbd, bean));

前面也提到过这个方法,它会将需要提前曝光的单例加入到缓存中,将单例的 beanNamebeanFactory 加入到缓存,在之后需要用到的时候,直接从缓存中取出来。


populateBean 填充属性

刚才第一步时也说过了,一开始创建的只是初始 bean,没有属性值,所以在这一步会解析类的属性。在属性解析时,会判断属性的类型,如果判断到是 RuntimeBeanReference 类型,将会解析引用。

就像我们写的例子, CircleA 引用了 CircleB,在加载 CircleA时,发现 CircleB 依赖,于是乎就要去加载 CircleB

我们来看下代码中的具体流程吧:

  1. protectedvoid populateBean(String beanName,RootBeanDefinition mbd,@NullableBeanWrapper bw){
  2.    ...
  3.    if(pvs !=null){
  4.        // 将属性应用到 bean 中,使用深拷贝,将子类的属性一并拷贝
  5.        applyPropertyValues(beanName, mbd, bw, pvs);
  6.    }
  7. }

  8. protectedvoid applyPropertyValues(String beanName,BeanDefinition mbd,BeanWrapper bw,PropertyValues pvs){
  9.    ...
  10.    String propertyName = pv.getName();
  11.    Object originalValue = pv.getValue();
  12.    // 注释 5.5 解析参数,如果是引用对象,将会进行提前加载
  13.    Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
  14.    ...
  15. }

  16. publicObject resolveValueIfNecessary(Object argName,@NullableObject value){
  17.    // 我们必须检查每个值,看看它是否需要一个运行时引用,然后来解析另一个 bean
  18.    if(value instanceofRuntimeBeanReference){
  19.        // 注释 5.6 在这一步中,如果判断是引用类型,需要解析引用,加载另一个 bean
  20.        RuntimeBeanReference ref =(RuntimeBeanReference) value;
  21.        return resolveReference(argName, ref);
  22.    }
  23.    ...
  24. }

跟踪到这里,加载引用的流程比较清晰了,发现是引用类的话,最终会委派 org.springframework.beans.factory.support.BeanDefinitionValueResolver#resolveReference 进行引用处理,核心的两行代码如下:

  1. // 注释 5.7 在这里加载引用的 bean
  2. bean =this.beanFactory.getBean(refName);
  3. this.beanFactory.registerDependentBean(refName,this.beanName);

在这一步进行 CircleB 的加载,但是我们写的例子中, CircleB 依赖了 CircleA,那它是如何处理的呢,所以这时,我们刚才将 CircleA 放入到缓存中的信息就起到了作用。


getSingleton

还记得之前在类加载时学到的只是么,单例模式每次加载都是取同一个对象,如果在缓存中有,可以直接取出来,在缓存中没有的话才进行加载,所以再来熟悉一下取单例的方法:

  1. protectedObject getSingleton(String beanName,boolean allowEarlyReference){
  2.    Object singletonObject =this.singletonObjects.get(beanName);
  3.    // 检查缓存中是否存在实例
  4.    if(singletonObject ==null&& isSingletonCurrentlyInCreation(beanName)){
  5.        // 记住,公共变量都需要加锁操作,避免多线程并发修改
  6.        synchronized(this.singletonObjects){
  7.            // 如果此 bean 正在加载则不处理
  8.            singletonObject =this.earlySingletonObjects.get(beanName);
  9.            if(singletonObject ==null&& allowEarlyReference){
  10.                // 当某些方法需要提前初始化,调用 addSingletonFactory 方法将对应的
  11.                // objectFactory 初始化策略存储在 earlySingletonObjects,并且从 singletonFactories 移除
  12.                ObjectFactory<?> singletonFactory =this.singletonFactories.get(beanName);
  13.                if(singletonFactory !=null){
  14.                    singletonObject = singletonFactory.getObject();
  15.                    this.earlySingletonObjects.put(beanName, singletonObject);
  16.                    this.singletonFactories.remove(beanName);
  17.                }
  18.            }
  19.        }
  20.    }
  21.    return singletonObject;
  22. }

虽然 CircleB 引用了 CircleA,但在之前的方法 addSingletonFactory 时, CircleAbeanFactory 就提前暴露。

所以 CircleB 在获取单例 getSingleton() 时,能够拿到 CircleA 的信息,所以 CircleB 顺利加载完成,同时将自己的信息加入到缓存和注册表中,接着返回去继续加载 CircleA,由于它的依赖已经加载到缓存中,所以 CircleA 也能够顺利完成加载,最终整个加载操作完成~

结合解决场景的流程图和关键代码流程,比较完善的介绍了循环依赖处理方法,下面还有一个 debug 流程图,希望能加深你的理解~

53.jpg

总结

写这篇总结的目的是为了填坑,因为之前在解析类加载的文章中只是简单的过了一下循环依赖的概念,想要将在类加载中留下的坑填掉。

在分析循环依赖的过程中,发现之前对作用域 scope 的不了解,于是补充了一下这个知识点,接着又发现对循环依赖中使用到的缓存和详细处理不熟悉,于是查阅了相关资料,跟踪源码,一步一步进行分析,所以发现越写越多,解决了一个困惑,增加了几个疑问,所以在不断排查和了解中,加深了对 Spring 的理解。

同样,在工作中,经常会遇到与其它团队的合作,也会遇到同时需要对方的新接口支持,例如在 RPC 中遇到循环调用,那我建议还是换一种方案,例如通过消息解耦,避免循环调用,实在没办法要循环调用,要记得在方法中加上退出条件,避免无限循环(>_<)


由于个人技术有限,如果有理解不到位或者错误的地方,请留下评论,我会根据朋友们的建议进行修正

代码和注释都在里面,小伙伴们可以下载我上传的代码,亲测可运行~

Gitee 地址:https://gitee.com/vip-augus/spring-analysis-note.git

Github 地址:https://github.com/Vip-Augus/spring-analysis-note


参考资料

  1. Spring学习(十五)Spring Bean 的5种作用域介绍
  2. Spring IOC 容器源码分析 - 循环依赖的解决办法
  3. Spring 源码深度解析》- 郝佳
相关文章
|
2月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
87 2
|
2月前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
2月前
|
缓存 架构师 Java
图解 Spring 循环依赖,一文吃透!
Spring 循环依赖如何解决,是大厂面试高频,本文详细解析,建议收藏。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
图解 Spring 循环依赖,一文吃透!
|
1月前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
54 2
|
2月前
|
前端开发 Java 开发者
Spring生态学习路径与源码深度探讨
【11月更文挑战第13天】Spring框架作为Java企业级开发中的核心框架,其丰富的生态系统和强大的功能吸引了无数开发者的关注。学习Spring生态不仅仅是掌握Spring Framework本身,更需要深入理解其周边组件和工具,以及源码的底层实现逻辑。本文将从Spring生态的学习路径入手,详细探讨如何系统地学习Spring,并深入解析各个重点的底层实现逻辑。
71 9
|
3月前
|
前端开发 Java 数据库
SpringBoot学习
【10月更文挑战第7天】Spring学习
45 9
|
2月前
|
Java Kotlin 索引
学习Spring框架特性及jiar包下载
Spring 5作为最新版本,更新了JDK基线至8,修订了核心框架,增强了反射和接口功能,支持响应式编程及Kotlin语言,引入了函数式Web框架,并提升了测试功能。Spring框架可在其官网下载,包括文档、jar包和XML Schema文档,适用于Java SE和Java EE项目。
36 0
|
3月前
|
XML Java 数据格式
Spring学习
【10月更文挑战第6天】Spring学习
29 1
|
3月前
|
Java 测试技术 开发者
springboot学习四:Spring Boot profile多环境配置、devtools热部署
这篇文章主要介绍了如何在Spring Boot中进行多环境配置以及如何整合DevTools实现热部署,以提高开发效率。
116 2
|
3月前
|
前端开发 Java 程序员
springboot 学习十五:Spring Boot 优雅的集成Swagger2、Knife4j
这篇文章是关于如何在Spring Boot项目中集成Swagger2和Knife4j来生成和美化API接口文档的详细教程。
298 1