当前位置: 首页 > news >正文

基础 | Spring - [单例创建过程]

INDEX

      • §1 相关类与容器
      • §2 过程
      • §3 总结

§1 相关类与容器

SpringApplication 是 springboot 应用的启动类的引导类
在这里插入图片描述
run()SpringApplication 中,我们主要使用的方法

PostProcessor 是 springboot 在初始化过程中的重要组件,包含多种类型,如 BeanFactoryPostProcessorsBeanPostProcessors 分别用来完成 bean 工厂和 bean 的初始化动作
一些与 springboot 整合的框架也会频繁使用类似的方式,比如 KafkaListenerAnnotationBeanPostProcessor

AbstractApplicationContext 在 springboot 中扮演重要角色,大部分启动流程实际是通过这个类完成的

ConfigurableListableBeanFactory 是 springboot 实例工厂的接口
其默认实现为 DefaultListableBeanFactory,主要依赖它来实例化对象

DefaultListableBeanFactory 继承自 AbstractBeanFactory
关键方法 doGetBeancreateBean 在这个抽象类的中
doGetBean 在此类中实现,方法定义了从尝试直接获取对象,到创建对象的流程
doGetBean 在此类中定义,在子类 AbstractAutowireCapableBeanFactory 中实现,用于真正创建实例

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
   	implements AutowireCapableBeanFactory {

DefaultSingletonBeanRegistry 是 Spring 用于单例模式的 bean 注册器

singletonObjects earlySingletonObjectssingletonFactoriesDefaultSingletonBeanRegistry 中的 3 个容器,如下代码

  • 在单例 SpringBean 的构建过程中,这三个容器作为缓存使用
  • singletonFactories 工厂缓存,作为三级缓存,存放 beanName<–>bean factory
  • earlySingletonObjects 早期预览缓存,作为二级缓存,存放 beanName<–>early bean(没有完全创建完)
  • singletonObjects 完全缓存,作为一级缓存,存放 beanName<–>bean(完全创建完)
/** Cache of singleton objects: bean name to bean instance. */
//1级, 已经完全实例化好了的 bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/** Cache of early singleton objects: bean name to bean instance. */
//2级, 没有完全实例化好的 bean 
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

/** Cache of singleton factories: bean name to ObjectFactory. */
//3级, bean 的工厂
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

§2 过程

在这里插入图片描述
在这里插入图片描述

进入 SpringApplication.run,通过 refreshContext 刷新容器
在这里插入图片描述
然后进入 AbstractApplicationContext.refresh
在这里插入图片描述
这一步骤中注册 spring bean 的后置处理器
依赖注入,基于其中一个 spring bean 的后置处理器 实现
在这里插入图片描述

然后进入 AbstractApplicationContext.finishBeanFactoryInitialization ,完成 spring bean 工厂的初始化
在这里插入图片描述
此方法的最后一步,就是初始化所有非懒加载的单例对象
在这里插入图片描述
这里的 beanFactoryDefaultSingletonBeanRegistry
从下图位置 preInstantiateSingletons 开始,springboot 开始实例化非懒加载的单例 beans
为了调试方便,这里使用 条件断点,条件为 bean 的 name,是我们想关注的那几个(否则会有很多 springboot 自己的对象)
在这里插入图片描述
1 处,判断当前这个 beanName 对应的是个单例的对象,不是就 pass
2 处,判断当前这个 beanName 对应的是个 工厂,肯定不是,走 else
3 处,正常单例无误,开始获取(获取不到肯定就得创造)
在这里插入图片描述
路过若干个重载的 getBean 最终来到 AbstractBeanFactory.doGetBean
在红框位置,检查当前 Bean 是否存在于缓存中,但此时的 bean 还没有标记为创建中,因此被拒绝,如下图红叉处
在这里插入图片描述
在这里插入图片描述

回到 AbstractBeanFactory.doGetBean 后检查有没有在当前工厂里已经定义了,这里其实是从父工厂里进行检查
看上面的红框,父工厂都是 null ,跳过了
看下面的红框,如果不是只校验类型不校验引用,会将当前 bean 标记为 创建中
在这里插入图片描述

接下来检查 bean 是否有 depend on 的限制,当前案例肯定是没有的,跳过
在这里插入图片描述

接下来是三种对象的类型的构建,分别是 单例原型其他
单例,2 处进入创建实例的逻辑,但需要注意,这个逻辑只是定义在红线的匿名内部类中,实际调用时机是 3 处的方法内

在这里插入图片描述

原型,可见多了一个 beforePrototypeCreation 方法 和后面的 afterPrototypeCreation
在这里插入图片描述

其他,从 1 处可见,在这种情况下,springboot 是懵逼的,所以一言不合就找机会扔异常
同时,从 2 处可见,在这种情况下,4 处提供的 getObject 方法是按照 原型 的过程创建对象的
可以理解为这种创建过程是对 原型 的微调和别名
在这里插入图片描述

可以看到默认的其他类型 socpes 如下图所示,可以理解为什么以 原型为模板,因为不可能只有一个 请求 吧
在这里插入图片描述
而 其他 scope 和原型的区别在于黄圈的 3 处,这里对于每一种 scope 定义自己 getObject 的行为
这个行为命名为 get 方法,并在 Scope.get 中 调用绿圈 4 处定义的 getObject 的逻辑(即 2 处的逻辑)

当前场景肯定是执行 单例 分支,即上面 单例截图 3 处
来到 DefaultSingletonBeanRegistry.getSingleton 可见 单例截图 的 2 处其实就是下图 1 处的入参,并在下图 2 处被使用

DefaultSingletonBeanRegistry.getSingleton
注意上图的 beforeSingletonCreation 更下面还有一个 afterSingletonCreation 可见 beforeXXCreationcreateBeanafterXXCreation 是 springboot 创建实例的套路
同时此方法将正在创建的 bean 标记为 创建中的单例( 放进 singletonsCurrentlyInDestruction ),这里就是上面 getSingleton 没有通过的地方 ,点此跳转,第二次就不会被此条件拒绝
在这里插入图片描述
DefaultSingletonBeanRegistry 类上文说过,是实例化单例对象的重要类,里面有三个 map,作为三级缓存
为了方便观察,添加下面的变量
在这里插入图片描述
从此位置,即 DefaultSingletonBeanRegistry.getSingleton的 2 处 (点击跳转)
在这里插入图片描述
进入上文 单例截图,即 AbstractBeanFactory.doGetBean 的 2 处(点击跳转)
进而进入 AbstractAutowireCapableBeanFactory 接下来是一大串这个类中逻辑
从上文AbstractBeanFactory.doGetBean 会进入此类的 createBean 中,可以发现其中有个 doCreateBean

一般,doXX 方法是 XX 方法中的干货
因为一些比较周边的逻辑确实算是 XX 逻辑的一部分,但是这些逻辑没有实际解决问题(比如参数校验啥的) ,在这些逻辑中间通常夹着实际解决问题的逻辑(可以简称为干货逻辑)
而这两种逻辑混在一起并且很长时,阅读者很难区分二者,这时,需要将干货逻辑抽取方法,单独存在
但是,这个干货方法的作用实际上就是 XX,于是常规套路命名为 doXX 以作区分(和强调)

在这里插入图片描述

首先在 doCreateBean 中,尝试创建对象
在这里插入图片描述
获取 bean 的类型,随后通常在下图 2 处尝试是否指定了非空参构造器,如果没有,在 1 处用空参构造器创建对象并返回
在这里插入图片描述
在这里插入图片描述
然后在 doCreateBean 中,判断当前是否可能存在循环依赖
如果可能(正创建的 bean 是单例,系统参数运行并且已经标记为创建中),则需要将当前 bean 加入工厂缓存,如下图红框
在这里插入图片描述
具体逻辑如下
在这里插入图片描述
此时可以从以前的表达式看到 AService 已经进入工厂缓存
在这里插入图片描述

上图中有个从早期预览缓存删除的逻辑,这是因为在循环依赖场景,标记了 bean 为创建中,但还没有创建出实例(没在完全缓存中),同时系统参数允许,就从这里快速获得,同时将单例放到此缓存中,从注释看,涉及到一个 full singleton lock
在这里插入图片描述

接下来 springboot 会尝试给刚刚创建的 AService 的 bean 填充属性(populate 有输入数据的意思)
在这里插入图片描述
进入此方法,先尝试获取属性值,现在当然没有
在这里插入图片描述
判断是否有创建 bean 过程中需要使用的 beanPostProcessor
在这里插入图片描述
从下面的监控里,可以看到有 4 个,其中明显红框处是当前场景真正需要的

  • 使用 @Resource 时,通过 CommonAnnotationBeanPostProcessor 完成注入
  • 使用 @Autowired 时,通过 AutowiredAnnotationBeanPostProcessor 完成注入
    在这里插入图片描述

循环至 CommonAnnotationBeanPostProcessor,进入 postProcessProperties ,如图中步骤
这里发现 bean,即实例化出来的 AService 中有个 BServiceb 变量
并通过下面的 inject 方法注入
在这里插入图片描述
InjectionMetadata.inject 通过其内部类 InjectedElement 完成注入
在这里插入图片描述
在这里,获取用于注入的资源,后面如果获取不到就创建,整个过程经过 CommonAnnotationBeanPostProcessor 内部类 ResourceElement.getResourceToInjectCommonAnnotationBeanPostProcessor.getResourceCommonAnnotationBeanPostProcessor.autowireResourceDefaultListableBeanFactory.resolveDependency
最终进入 DefaultListableBeanFactory.doResolveDependency,如下面系列截图
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
最终到达 DefaultListableBeanFactory.doResolveDependency
在这里插入图片描述
doResolveDependency 会在获取注入类型后尝试获取建议值
在这里插入图片描述
如下图,这个建议值是在注解上获取的,可能对应 @Resoutce("xxx") 的场景
在这里插入图片描述
在下图取得了需要注入的对象的类型
在这里插入图片描述
这里有两种可能性:单实现多实现
在这里插入图片描述
一般情况都是单实现,进入 DependencyDescriptor.resolveCandidate 方法
在这里插入图片描述
进去后看到一个有点眼熟的东西,继续向下,又一次进入 AbstractBeanFactory.doGetBean
这里已经是创建被依赖的对象的 bean 了
在这里插入图片描述
在这里插入图片描述
路过这里
在这里插入图片描述
又一次通过 DefaultSingletonBeanRegistry.getSingleton 调用 AbstractBeanFactory.doGetBean 的匿名内部类,进入其子类 AbstractAutowireCapableBeanFactory.createBean
在这里插入图片描述
随后又进入其中的 doCreateBean,并在红框处创建实例
在这里插入图片描述
并判断当前是不是可能出现循环依赖,如果是,继续扔到工厂缓存中,如下图 1 处
在这里插入图片描述
在这里插入图片描述

然后给当前 bean 填充属性,如上图 2 处,注意 此时的当前 bean 已经是 BService

在这里插入图片描述

有一次循环至 CommonAnnotationBeanPostProcessor,进入 postProcessProperties,通过 InjectionMetadata.inject 与其内部类 InjectedElement 注入
在这里插入图片描述
在这里插入图片描述

尝试注入时,继续获取被注入值的资源

在这里插入图片描述
和创建 AService 时一样,路过 CommonAnnotationBeanPostProcessor 内部类 ResourceElement.getResourceToInjectCommonAnnotationBeanPostProcessor.getResourceCommonAnnotationBeanPostProcessor.autowireResourceDefaultListableBeanFactory.resolveDependency
最终进入 DefaultListableBeanFactory.doResolveDependency,执行下图代码
在这里插入图片描述
第三次通过 getBean,此时又是获取 AService,如下图
在这里插入图片描述
在这里插入图片描述

但这时,AService 已经被标记为创建中,于是通过下图 √ 处的校验,并在 1、2、3 处依次查找各级缓存
如果是在工厂缓存中找到的 bean ,就对它做一次缓存升级,从 工厂缓存 提升到 早期预览缓存
在这里插入图片描述
如此图效果
在这里插入图片描述
而回到 AbstractBeanFactory.doGetBean 时,第二次 getBean( AService ) 已经获取到了它的 bean
在这里插入图片描述
于是跳过了上次 doGetBean 时走的大段创建过程,到最后一步,从这里返回
在这里插入图片描述
回到了 BService 执行了一半的 DefaultListableBeanFactory.doResolveDependency
在这里插入图片描述

回到了 CommonAnnotationBeanPostProcessor.autowireResource 并在后面注册依赖
在这里插入图片描述
在这里插入图片描述
从下面变量看,都注册完成了
在这里插入图片描述
继续运行,回到了 InjectionMetadata.InjectedElement.inject 通过红框完成注入(红框底层是 Unsafe 类)
在这里插入图片描述
在这里插入图片描述

此时,回到 AbstractAutowireCapableBeanFactory.populateBean 应用属性值(但不是注入的意思,之前已经注入成功了)
在这里插入图片描述

继续向后,完成 BServicecreateBean
在这里插入图片描述

即完成了 DefaultSingletonBeanRegistry.getSingleton 中 这一步
在这里插入图片描述
并在 DefaultSingletonBeanRegistry.getSingleton 最后添加 BService 进完成缓存
在这里插入图片描述
Bservice 进入完成缓存,然后删除 工厂缓存(还删了早期预览缓存,并增加了注册单例,但现在不关心)
在这里插入图片描述

在这里插入图片描述

退出 AbstractBeanFactory.doGetBean 完成 BService 的创建,并回到 DependencyDescriptor.resolveCandidate 方法

在这里插入图片描述

在这里插入图片描述
继续,就回到了 DependencyDescriptor.resolveCandidate 并取得了填充 AService 属性 b 的 BService bean
在这里插入图片描述
然后和注册 AService 一样,注册 BService 为依赖,接着完成注入
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
最后将 AService 加入完成缓存,完成单例
在这里插入图片描述
在这里插入图片描述
需要注意理解的是,此时,DefaultSingletonBeanRegistry.preInstantiateSingletons 只是在下图的循环中完成了 AService 的创建 这一轮循环,下面会轮到 BService 的创建 的轮次,但 BService 早就创建完成了
在这里插入图片描述

§3 总结

springboot 开始实例化非懒加载单例 bean
springboot 实例化 A getBean 的轮次开始

A doGetBean
A 从缓存中获取失败,因为 A 没有标记为创建中
A 实例化
A 标记为创建中,进入 工厂缓存
A 填充属性,通过 postProcessor
获取 A 需要填充的字段,并尝试解决它
尝试解决它 A 需要填充的字段的类型
尝试解决它 A 需要填充的字段的资源(Resource),DependencyDescriptor.resolveCandidate
触发 B 的 getBean

B doGetBean
B 从缓存中获取失败,因为 B 没有标记为创建中
B 实例化
B 标记为创建中,进入 工厂缓存
B 填充属性,通过 postProcessor
获取 B 需要填充的字段,并尝试解决它
尝试解决它 B 需要填充的字段的类型
尝试解决它 B 需要填充的字段的资源(Resource),DependencyDescriptor.resolveCandidate
触发 A 的 getBean

A doGetBean
A 从缓存中获取成功,返回
B 需要填充的字段的资源(Resource) 获取成功
回到 postProcessor,完成 B 中 a 的注入
B 添加到完成缓存,完成 B 的单例注册
B 创建完成(实例化完成)

==回到 A 触发的 B 的 getBean ==(已完成)
A 需要填充的字段的资源(Resource)已经有了,刚刚 getBean 返回的就是
回到 postProcessor,完成 A 中 b 的注入
A 添加到完成缓存,完成 B 的单例注册
A 创建完成(实例化完成)

springboot 实例化 A getBean 的轮次结束
springboot 实例化 B getBean 的轮次结束

B doGetBean
B 从缓存中获取成功

springboot 实例化 B getBean 的轮次结束

相关文章:

  • K8S集群Pod资源自动扩缩容方案
  • SPPNet
  • java多线程-多线程技能
  • 网课查题接口 该怎么搭建
  • Elasticsearch学习-- 聚合查询
  • 网课搜题公众号接口
  • ubuntu18.04.1LTS 编译安装ffmpeg详解
  • 接口幂等问题:redis分布式锁解决方案
  • 算法与数据结构(第一周)——线性查找法
  • 修改docker 修改容器配置
  • ARM汇编语言
  • 【通信】非正交多址接入(NOMA)和正交频分多址接入(OFDMA)的性能对比附matlab代码
  • 深入理解控制反转IOC和依赖注入
  • micropython 可视化音频 频谱解析(应该是全网首家)(预告,还没研究完成)
  • 网课答案接口平台 系统独立后台
  • 「面试题」如何实现一个圣杯布局?
  • 11111111
  • canvas 绘制双线技巧
  • Java 内存分配及垃圾回收机制初探
  • javascript 总结(常用工具类的封装)
  • Javascript基础之Array数组API
  • JavaScript设计模式系列一:工厂模式
  • ng6--错误信息小结(持续更新)
  • Terraform入门 - 3. 变更基础设施
  • use Google search engine
  • Vultr 教程目录
  • 包装类对象
  • 工作踩坑系列——https访问遇到“已阻止载入混合活动内容”
  • 深度学习入门:10门免费线上课程推荐
  • 手机端车牌号码键盘的vue组件
  • 手写一个CommonJS打包工具(一)
  • 一个6年java程序员的工作感悟,写给还在迷茫的你
  • 正则与JS中的正则
  • 如何通过报表单元格右键控制报表跳转到不同链接地址 ...
  • ​Java并发新构件之Exchanger
  • ​软考-高级-信息系统项目管理师教程 第四版【第23章-组织通用管理-思维导图】​
  • #基础#使用Jupyter进行Notebook的转换 .ipynb文件导出为.md文件
  • (delphi11最新学习资料) Object Pascal 学习笔记---第8章第5节(封闭类和Final方法)
  • (独孤九剑)--文件系统
  • (附源码)ssm经济信息门户网站 毕业设计 141634
  • (附源码)基于ssm的模具配件账单管理系统 毕业设计 081848
  • (附源码)计算机毕业设计高校学生选课系统
  • (推荐)叮当——中文语音对话机器人
  • .bat批处理(八):各种形式的变量%0、%i、%%i、var、%var%、!var!的含义和区别
  • .NET MVC、 WebAPI、 WebService【ws】、NVVM、WCF、Remoting
  • .net 使用ajax控件后如何调用前端脚本
  • .Net 知识杂记
  • .NET中winform传递参数至Url并获得返回值或文件
  • .NET中的Event与Delegates,从Publisher到Subscriber的衔接!
  • @SuppressWarnings(unchecked)代码的作用
  • []C/C++读取串口接收到的数据程序
  • []FET-430SIM508 研究日志 11.3.31
  • [Angularjs]asp.net mvc+angularjs+web api单页应用之CRUD操作
  • [C++] 多线程编程-thread::yield()-sleep_for()
  • [docker] Docker的私有仓库部署——Harbor