写好 Spring Starter : 控制好Bean的加载顺序与原理

news/2024/4/26 4:24:48/文章来源:https://blog.csdn.net/Huangjiazhen711/article/details/127610604

一 .前言

想写好一个 Starter , 控制配置的加载和Bean的加载是其中至关重要的一步.

这一篇把如何做好Bean管理做了一个总结 , 来好好看看Bean如何控制顺序.

二. 基础篇 - Bean 的控制

Bean 名称控制

  • 同一个包里面 Bean 名称根据字母优先级排序 ,是可以控制Bean的加载流程
  • 不同包里面 按照包名层级有序加载
  • 不同依赖包 和包的加载顺序有关

注解控制

对Bean加载顺序起作用的注解主要有 : @DependsOn , @Bean

  • @Bean 主要还是和方法的加载有关 , 由上到下自然加载
  • @DependsOn 用于控制互相依赖 , 先加载依赖的包
  • @SpringBootApplicationscanBasePackages 可以控制包有序

特别注意

  • @Resouce 和 @Autowired 并不能控制依赖关系
  • 构造器中 @Autowired 可以影响到加载顺序

2.1 Bean 加载流程简化版

三. 特别要点原理分析

3.1 包路径有序的控制

我们可以知道这里有两个阶段 :

  • 阶段一 : 通过 registerBeanDefinition 往 Map 中放 BeanDefinition , 同时往一个 List 中放入名称
  • 阶段二 : 取出 BeanDefinition 后逐条加载Bean

这里面其实涉及到几个点 :

S1 : ClassPathBeanDefinitionScanner 中 doScan 时是按照 @SpringBootApplication 配置的扫描路径进行扫描

S2 : DefaultListableBeanFactory # registerBeanDefinition 时是放入有序的 list

总结 : 这里可以看到 , 从package 扫描到后续加载都是 list 控制的有序 , 所以 @Bean 的加载是有序的 , 并且我们可以通过 scanBasePackages 注解控制相对有序!!!

3.2 @Bean 的加载

  • S1 : 首先是3.1中说到的 , 按照包名去加载 Bean
  • S2 : 在 ConfigurationClassBeanDefinitionReader 处理 Configuration 类时会讲方法作为BeanNames

这里其实很多文章里面说的是 @Bean 并不是写在前面的会先加载 ,但是在我个人读取源码的过程中 , 发现@Bean的书写顺序确实是能控制顺序的 , 从源码的角度来分析一下 :

S1 : Spring 发现该@Bean是一个配置类 , 后续触发ConfigurationClassBeanDefinitionReader对配置类进行读取
S2 : doProcessConfigurationClass 中 retrieveBeanMethodMetadata 获取到所有的 Set<MethodMetadata>
S3 : 循环所有的 Set<MethodMetadata> , 将 BeanName 放入 List 中 

整个过程中最重要的就是 S2 , 虽然它是个 Set ,但是看看源码就知道 , 这是一个 LinkedHashSet , 所以它其实是有序的

private Set<MethodMetadata> retrieveBeanMethodMetadata(SourceClass sourceClass) {AnnotationMetadata original = sourceClass.getMetadata();// 通过 @Bean 获取到所有的 MethodMetadata , 这里的 Set 实际上是 LinkdedHashSetSet<MethodMetadata> beanMethods = original.getAnnotatedMethods(Bean.class.getName());if (beanMethods.size() > 1 && original instanceof StandardAnnotationMetadata) {AnnotationMetadata asm =this.metadataReaderFactory.getMetadataReader(original.getClassName()).getAnnotationMetadata();Set<MethodMetadata> asmMethods = asm.getAnnotatedMethods(Bean.class.getName());// .............. 省略一些其他场景}}return beanMethods;
}

实际上在我更换 @Configuration 中 @Bean 的书写顺序时 , 得到的效果也和源码中看的一致.

3.3 @DependsOn 的生效原理

DependsOn 的处理应该算是在 Spring Bean 加载流程的第二阶段处理的.

在这个阶段中SpringIOC获取Bean的BeanDefinition , 并且对Bean类型进行分析同时判断其关联关系.

这个流程主要在 AbstractBeanFactory 中实现.

C- AbstractBeanFactory # doGetBean
// 这里获取的就是@DependsOn 注解中的方法名
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {for (String dep : dependsOn) {// S1 : 这里需要判断这个依赖是否已经注册, 会从 S2 里面的Map 中获取if (isDependent(beanName, dep)) {throw new BeanCreationException(.....); }     // S2 : 这里把DependsOn 的Bean注册到Map中 ,避免重复registerDependentBean(dep, beanName);// S3 : 递归获取 , 这里就是按照Bean全流程递归处理getBean(dep);}
}

3.4 不是说循环依赖会优先加载吗 , 为什么引用@Resource 并没有控制顺序

在以往很多场景中 ,我都是通过@Resource 引入某个对象 , 来实现优先加载 . 但是事实证明这种方式是不生效的.

这里面涉及到Spring循环依赖的相关特点 ,即 Spring 依赖时创建的对象 , 还没有被初始化了.

但是如果在构造器中引入依赖 , 是可以影响到Bean的加载顺序的 :

@Bean
public BeanThree getBeanThree(BeanTwo beanTwo) {return new BeanThree(beanTwo);
}

这是简单说一下原理 , 在构造器载入的场景下 , Spring 需要先将依赖的对象初始化后 , 再作为 argsToUse 传入被构建的对象中 :

// argsToUse 即为构造器中的对象
bw.setBeanInstance(instantiate(beanName, mbd, factoryBean, factoryMethodToUse, argsToUse));

流程跟踪 :

// S1 : Spring 容器初始化 BeanThree
C- AbstractAutowireCapableBeanFactory # doCreateBean- instanceWrapper = createBeanInstance("getBeanThree", mbd, args);// S2 : 通过构造器进行初始化
C- AbstractAutowireCapableBeanFactory # instantiateUsingFactoryMethod- return new ConstructorResolver(this).instantiateUsingFactoryMethod(beanName, mbd, explicitArgs);// S3 :  初始化 BeanThree 前先初始化 BeanTwo
public BeanWrapper instantiateUsingFactoryMethod(String beanName, RootBeanDefinition mbd, @Nullable Object[] explicitArgs) {// 3-1 省略获取工厂的相关逻辑 // 3-2 获取到构造器方法- public .....ConfigutationA.getBeanThree(.....BeanTwo)Method factoryMethodToUse = null;ArgumentsHolder argsHolderToUse = null;Object[] argsToUse = null;//... 省略   if (factoryMethodToUse == null || argsToUse == null) {List<Method> candidates = null;if (mbd.isFactoryMethodUnique) {if (factoryMethodToUse != null) {// 此处获取所有的构造器方法candidates = Collections.singletonList(factoryMethodToUse);}}//... 省略额外的构造器处理minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);LinkedList<UnsatisfiedDependencyException> causes = null;for (Method candidate : candidates) {// 3-3 此处就开始处理构造器中的所有参数 , 以及需要autowired处理的参数// - 在这个环节中 , 会优先调用依赖的对象的初始化操作argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw,paramTypes, paramNames, candidate, autowiring, candidates.size() == 1);}}// S3-4 继续调用 BeanThree 初始化逻辑bw.setBeanInstance(instantiate(beanName, mbd, factoryBean, factoryMethodToUse, argsToUse));return bw;
}// S4 : 初始化 BeanTwo
// S5 : 继续初始化 BeanThree

总结 : 可以看到 , 如果构造器中存在了需要初始化后才能注入的对象 ,是会触发提前加载. 但是如果单纯的类中的属性 , 实际上是在后面 populationBean 中进行的处理

总结

Bean 控制有序的方式还是有很多种的 , 以上的加载方式只能说以Idea启动时能确定是存在影响的 , 但是会不会由于某种场景例如JVM的加载等等破坏这种操作 , 暂时是不清楚的.

同时是否会由于一些代理等操作影响到这种加载 , 也存在不确定.

不过如果遇到需要控制Bean加载的场景 , 不妨试试上面的办法 , 说不定能满足需求.

下一篇来看一下配置文件如何进行有序的控制.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_410098.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Nuttx学习笔记(二)————在STM32上部署Nuttx系统

目录 一、平台配置 二、在ubuntu下使用串口来烧录至目标文件至STM32F07 &#xff08;一&#xff09;ubuntu下stm32flash工具下载 &#xff08;二&#xff09;Ubuntu20.04安装stm32开发环境 &#xff08;三&#xff09;将nuttx.bin文件烧录进stm32 三、ubuntu下使用OpenOCD…

工厂人员着装识别检测

工厂人员着装识别检测&#xff0c;依据智能视频分析和神经网络算法技术&#xff0c;实时分析和识别现场监控视频画面信息。工厂人员着装识别检测针对不穿工装的行为及时报警抓拍&#xff0c;将警报截屏和视频保存到数据库系统中发给后台&#xff0c;并把违规记录推送到有关人员…

基于jeecgboot的flowable流程支持online表单(二)

这部分很多功能代码由网友撼动宇宙提供&#xff0c;这里先感谢这位网友的辛苦工作 这部分主要是online表单的显示与录入数据获取 1、先建两个表 -- ---------------------------- -- Table structure for bpm_tool_designer -- ---------------------------- DROP TABLE IF E…

Presto和Spark语法差异

一、同类实现差异 1、Presto整数相除沿用了Java整数相除的特性&#xff0c;而Spark除法会得到小数。 示例&#xff1a; select 5/2; Presto返回2&#xff0c;Spark返回2.5。 2、Presto的substr()函数的子字符串索引从1开始&#xff0c;而spark从0开始。 示例&#xff1a;…

用于一般光学系统的光栅元件

摘要 光栅是光学中最常用的衍射元件之一。如今&#xff0c;它们经常被用于复杂的系统中&#xff0c;并与其他元件一起工作。在这种情况下&#xff0c;非常需要将光栅不仅仅是作为孤立的元件来模拟&#xff0c;而是与系统的其余部分结合&#xff0c;以评估整个系统性能。Virt…

并发与多线程(4)单例设计模式共享数据分析 和call_once

一、单例模式 顾名思义就是一个项目中的某个类只有一个对象&#xff0c;不允许在外面new 出第二个对象 #if 1 //单例模式 :class MyClass { private:MyClass(){}static MyClass* m_instance; // public:static MyClass* getInstance(){if (m_instance NULL){m_instance …

推荐一个.Net Core轻量级插件架构

今天给大家推荐一个开源插件架构。在介绍项目之前&#xff0c;我们了解下什么是插件架构&#xff0c;它的用处。 现有的软件开发中&#xff0c;业务越来越复杂&#xff0c;一些大型的项目版本一直在迭代&#xff0c;代码规模越来越大&#xff0c;涉及的人员也越来越多&#xf…

电子江湖里,女攻城狮到底是一种怎样的存在?

关于电子工程师这一角色&#xff0c;女生真的不能胜任么&#xff1f;我觉得不然&#xff01; 虽然说出身电子信息类的女生并不算多&#xff0c;去到职场中就职且能坚持下去的更是少之又少&#xff0c;毕竟理工科嘛&#xff0c;加上真实存在的行业歧视&#xff0c;想要靠近的女生…

学长教你学C-day5-C语言变量与数据类型

小韩是一个学习比较刻苦认真的学生&#xff0c;虽然老师上课进度刚讲到输入输出&#xff0c;但是小韩已经自学到C语言指针部分的内容了。但是进度太快的弊端就是有些东西很难消化吸收&#xff0c;这不就遇到了问题&#xff0c;来请教小刘&#xff1a;“学长&#xff0c;你说这个…

机器学习——聚类分析

文章目录聚类分析K-means算法K-中心算法DBSCAN算法聚类分析 K-means算法 算法简要步骤 随机选取K个样本点&#xff08;不一定来自样本数据&#xff09;作为初始的质心第一次迭代&#xff0c;将所有样本分配到这K个类中 对每个样本计算其到两个聚类中心的欧式距离&#xff08;…

2022年12个最佳WordPress备份插件比较

您是否正在寻找可靠的WordPress备份插件来定期备份您的网站&#xff1f; 备份就像您网站的安全网。每当您的网站因任何原因崩溃时&#xff0c;您都可以快速恢复您的网站。但是您需要确保您的备份具有最新的更改&#xff0c;否则您可能会丢失重要数据。一个好的备份插件将确保您…

艾美捷藻红蛋白RPE化学性质文献参考

艾美捷藻红蛋白RPE背景&#xff1a; R-藻红蛋白是从海藻&#xff08;甘紫菜或高氏肠枝藻&#xff09;分离的藻胆蛋白家族成员。从红藻中分离得到 R- 藻红蛋白(PE)。其主吸收峰位于565nm&#xff0c;次吸收峰位于496nm 和545nm。次级峰的相对显著性在不同种的 R-PE 中差异显著。…

前端开发学习之【Vue】-下

文章目录Vuex1.概述2.使用3.四个 map 方法4.模块化命名空间Vue Router1.SPA2.路由3.基本使用4.多级路由5.路由传参query参数6.命名路由7.路由传参params参数8.路由的props配置9.路由跳转方式10.缓存路由11. activated deactivated生命周期钩子12.路由守卫13.路由器的两种工作模…

极限多标签算法: FastXML 的解析

文章目录前言1.关于极限多标签 (XML: eXtreme multi-label Classification)1.1 流派1.2 评价指标2.FastXML2.1 FastXML的特点2.2 FastXML的局部性2.3 FastXML的拟合目标2.4 通过代码分析FastXML的拟合细节2.4.1 r\mathbf{r}^{}r的优化与拟合2.4.2 δ\deltaδ的优化与拟合 ---- …

知识图谱-命名实体-关系-免费标注工具-快速打标签-Python3

知识图谱-命名实体-关系-免费标注工具-快速打标签-Python3一、功能介绍1、代码文件夹结构2、运行环境3、自定义命名实体、关系模板4、导入文件5、选择自定义实体和关系文件6、文本标注7、撤销和取消标注8、导出和导出并退出系统9、导出文件后解析10、标注规范和KG规范11、系统提…

SQL学习二十、SQL高级特性

约束&#xff08;constraint&#xff09; 管理如何插入或处理数据库数据的规则。 DBMS 通过在数据库表上施加约束来实施引用完整性。 大多数约束是在 表定义中定义的&#xff0c;用 CREATE TABLE 或 ALTER TABLE 语句。 1、主键 &#xff08;PRIMARY KEY&#xff09; 主键是…

AMCL代码详解(六)amcl中的重采样

1.重采样判断 上一章讲述了amcl中如何根据激光观测更新粒子权重&#xff0c;当粒子更新完后amcl会需要根据程序判断是否需要进行重采样。这个判断在粒子观测更新权重后进行判断&#xff0c;代码在amcl_node.cpp中&#xff1a; if(!(resample_count_ % resample_interval_)){ p…

[GYCTF2020]Easyphp

尝试了一下万能密码不行&#xff0c;又到处翻了一下&#xff0c;扫目录结果又有www.zip 审计代码好久&#xff0c;序列化和sql结合的题还是第一次见&#xff0c;太菜了呀&#xff0c;花了很久时间才理解这个题 首先看到update.php&#xff0c;这个文件是最亮眼的&#xff0c;…

javascript 原生类 DOMParser 把 字符串格式的HTML文档源码 转换成 document DOM对象

文章目录IntroQADOMParser 在 console 的使用cheerio 在 node 项目中的使用Reference测试sumIntro 有一天我在写爬虫。 其实也说不上是爬虫&#xff0c;就是打开浏览器上网&#xff0c;觉得页面有些数据挺有意思&#xff0c;就打开开发者工具&#xff0c;在 Network/Console 中…

01.初识C语言1

一、前期准备 1.gitee网址&#xff08;代码托管网站&#xff09;&#xff1a;工作台 - Gitee.com Git教程 - 廖雪峰的官方网站 (liaoxuefeng.com) 用法&#xff1a; 1&#xff09;新建仓库 2&#xff09;随意勾选 3&#xff09;网络仓库构建完成 2.所学知识&#xff1a;计算…