Springboot3 自动装配流程与核心文件:imports文件

注:本文以spring-boot v3.4.1源码为基础,梳理spring-boot应用启动流程、分析自动装配的原理

如果对spring-boot2自动装配有兴趣,可以看看我另一篇文章:
Springboot2 自动装配之spring-autoconfigure-metadata.properties和spring.factories(SPI机制核心)

1、启动入口

以下是源码里一段应用启动单元测试代码:

package org.springframework.boot.test.autoconfigure;
.../*** Tests for {@link ConditionReportApplicationContextFailureProcessor}.** @author Phillip Webb* @author Scott Frederick* @deprecated since 3.2.11 for removal in 3.6.0*/
@ExtendWith(OutputCaptureExtension.class)
@Deprecated(since = "3.2.11", forRemoval = true)
@SuppressWarnings("removal")
class ConditionReportApplicationContextFailureProcessorTests {@Testvoid loadFailureShouldPrintReport(CapturedOutput output) {SpringApplication application = new SpringApplication(TestConfig.class);application.setWebApplicationType(WebApplicationType.NONE);ConfigurableApplicationContext applicationContext = application.run();ConditionReportApplicationContextFailureProcessor processor = new ConditionReportApplicationContextFailureProcessor();processor.processLoadFailure(applicationContext, new IllegalStateException());assertThat(output).contains("CONDITIONS EVALUATION REPORT").contains("Positive matches").contains("Negative matches");}@Configuration(proxyBeanMethods = false)@ImportAutoConfiguration(JacksonAutoConfiguration.class)static class TestConfig {}
}

spring-boot3应用启动入口是SpringApplication的构造方法,这个构造方法里做了一些初始化,比较重要。如下:

	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {this.resourceLoader = resourceLoader;Assert.notNull(primarySources, "PrimarySources must not be null");// @Athis.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));// @Bthis.properties.setWebApplicationType(WebApplicationType.deduceFromClasspath());// @Cthis.bootstrapRegistryInitializers = new ArrayList<>(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));this.mainApplicationClass = deduceMainApplicationClass();}
  • @A:标签当前应用的启动主类,也就是我们平常写的xxxApplication类

  • @B:在类路径下查找是否有 :

    • private static final String WEBMVC_INDICATOR_CLASS = “org.springframework.web.servlet.DispatcherServlet”;
    • private static final String WEBFLUX_INDICATOR_CLASS = “org.springframework.web.reactive.DispatcherHandler”;
    • private static final String JERSEY_INDICATOR_CLASS = “org.glassfish.jersey.servlet.ServletContainer”;
      中的一个,标记当前web应用类型;web应用类型有:REACTIVE SERVLET NONE
  • @C:从类路径中可见的 spring.factories 文件中获取配置的BootstrapRegistryInitializer.class、ApplicationContextInitializer.class、ApplicationListener.class并缓存

2、启动核心方法 public ConfigurableApplicationContext run(String… args){}
public ConfigurableApplicationContext run(String... args) {Startup startup = Startup.create();if (this.properties.isRegisterShutdownHook()) {SpringApplication.shutdownHook.enableShutdownHookAddition();}// @ADefaultBootstrapContext bootstrapContext = createBootstrapContext();ConfigurableApplicationContext context = null;configureHeadlessProperty();// @BSpringApplicationRunListeners listeners = getRunListeners(args);listeners.starting(bootstrapContext, this.mainApplicationClass);try {// @CApplicationArguments applicationArguments = new DefaultApplicationArguments(args);ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);Banner printedBanner = printBanner(environment);// @Dcontext = createApplicationContext();context.setApplicationStartup(this.applicationStartup);// @EprepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);// @FrefreshContext(context);afterRefresh(context, applicationArguments);startup.started();if (this.properties.isLogStartupInfo()) {new StartupInfoLogger(this.mainApplicationClass, environment).logStarted(getApplicationLog(), startup);}listeners.started(context, startup.timeTakenToStarted());callRunners(context, applicationArguments);}catch (Throwable ex) {throw handleRunFailure(context, ex, listeners);}try {if (context.isRunning()) {listeners.ready(context, startup.ready());}}catch (Throwable ex) {throw handleRunFailure(context, ex, null);}return context;}
  • @A:创建DefaultBootstrapContext对象,逐个执行前面缓存中bootstrapRegistryInitializers的initialize方法
  • @B:从类路径META-INF/spring.factories配置文件中获取SpringApplicationRunListener配置的类(框架默认提供了EventPublishingRunListener)封装成SpringApplicationRunListeners,然后执行SpringApplicationRunListeners的starting方法,最终调用的是EventPublishingRunListener等配置类的starting方法
  • @C:创建ConfigurableEnvironment ,代码参看下面【第3章节】的prepareEnvironment方法
  • @D:创建ConfigurableApplicationContext对象,实现具体代码是:org.springframework.boot.DefaultApplicationContextFactory#create,默认情况下是返回的new AnnotationConfigApplicationContext() 这个对象
  • @E:准备各种Context,详情见下面代码【第4章节】的prepareContext方法
  • @F:刷新容器,这一块逻辑主要是执行spring-framework原生的refresh方法;这个核心方法主要做的内容就是解析bean定义,然后执行整个bean生命周期;具体流程可以看我的另一篇文章:
    • 链接: Spring 容器初始化源码跟读refresh01
    • 链接: Spring 容器初始化源码跟读refresh02
    • 链接: Spring 容器初始化源码跟读refresh03
    • 链接: Spring 容器初始化源码跟读refresh04
    • 链接: Spring 容器初始化源码跟读refresh05
    • 链接: Spring 容器初始化源码跟读refresh06
    • 链接: Spring 容器初始化源码跟读refresh07

在refresh阶段,就会执行spring-boot的自动装配的整个过程;

3、prepareEnvironment方法:创建ConfigurableEnvironment

接【第2章节】 @C 代码:

	private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {// Create and configure the environment// @AConfigurableEnvironment environment = getOrCreateEnvironment();// @BconfigureEnvironment(environment, applicationArguments.getSourceArgs());ConfigurationPropertySources.attach(environment);// @Clisteners.environmentPrepared(bootstrapContext, environment);ApplicationInfoPropertySource.moveToEnd(environment);DefaultPropertiesPropertySource.moveToEnd(environment);Assert.state(!environment.containsProperty("spring.main.environment-prefix"),"Environment prefix cannot be set via properties.");bindToSpringApplication(environment);if (!this.isCustomEnvironment) {EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader());environment = environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());}ConfigurationPropertySources.attach(environment);return environment;}

跟踪了一下代码:

  • @A:方法里执行了 new ApplicationEnvironment()创建对象并返回了,在构造方法里会触发StandardEnvironment#customizePropertySources方法,这个方法会把System.getProperties()的值放到环境的systemProperties环境值,然后把ProcessEnvironment.getenv()的值放到systemEnvironment环境值
  • @B:配置spring应用环境信息,解析启动命令里的参数
    • 创建一个ApplicationConversionService对象赋值到 ApplicationEnvironment的ConversionService属性;ApplicationConversionService主要作用是提供类型转换服务,可以将A类型数据转换为B类型数据。 细节可以参看这里: ConversionService介绍
    • 如果在启动参数中加了commandLineArgs参数,并且属性源(PropertySource)有commandLineArgs这个名称,则会在Environment里进行真实PropertySource替换;(这一块没太看懂具体要做的目的)
    • 最后创建一个ApplicationInfoPropertySource对象到ApplicationEnvironment的PropertySource链表中
  • @C:执行在【第2章节】创建的SpringApplicationRunListeners的environmentPrepared方法
4、prepareContext方法:准备各种Context,拉齐属性

接【第2章节】,DefaultBootstrapContext 是spring-boot的类,而 ConfigurableApplicationContext是spring-frampwork的类;这个方法会把两个类关联起来

private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,ApplicationArguments applicationArguments, Banner printedBanner) {// @Acontext.setEnvironment(environment);// @BpostProcessApplicationContext(context);addAotGeneratedInitializerIfNecessary(this.initializers);// @CapplyInitializers(context);// @Dlisteners.contextPrepared(context);bootstrapContext.close(context);if (this.properties.isLogStartupInfo()) {logStartupInfo(context.getParent() == null);logStartupInfo(context);logStartupProfileInfo(context);}// Add boot specific singleton beans// @EConfigurableListableBeanFactory beanFactory = context.getBeanFactory();beanFactory.registerSingleton("springApplicationArguments", applicationArguments);if (printedBanner != null) {beanFactory.registerSingleton("springBootBanner", printedBanner);}if (beanFactory instanceof AbstractAutowireCapableBeanFactory autowireCapableBeanFactory) {autowireCapableBeanFactory.setAllowCircularReferences(this.properties.isAllowCircularReferences());if (beanFactory instanceof DefaultListableBeanFactory listableBeanFactory) {listableBeanFactory.setAllowBeanDefinitionOverriding(this.properties.isAllowBeanDefinitionOverriding());}}if (this.properties.isLazyInitialization()) {context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());}if (this.properties.isKeepAlive()) {context.addApplicationListener(new KeepAlive());}// @Fcontext.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context));if (!AotDetector.useGeneratedArtifacts()) {// Load the sourcesSet<Object> sources = getAllSources();Assert.notEmpty(sources, "Sources must not be empty");load(context, sources.toArray(new Object[0]));}// @Glisteners.contextLoaded(context);}
  • @A:将刚刚创建的环境信息对象,设置到spring context里(即AnnotationConfigApplicationContext对象),使其和spring-boot的环境信息ConfigurableEnvironment对象保持一致
  • @B:这里继续对齐两个Context的属性,包括:
    • 1、向spring context注入bean定义:name为org.springframework.context.annotation.internalConfigurationBeanNameGenerator
    • 2、设置spring context的ResourceLoader和ClassLoader属性
    • 3、对齐sping容器属性:ConversionService conversionService,这个对象是在【第3章节】@B时机创建的一个ApplicationConversionService对象
  • @C:获取所有的ApplicationContextInitializer对象,并执行initialize方法;在initialize方法可以向ConfigurableListableBeanFactory bean工厂里注入一些自定义的bean定义或者其他bean工厂处理
  • @D:执行所有SpringApplicationRunListener子类的contextPrepared方法,框架默认提供的SpringApplicationRunListener子类是EventPublishingRunListener,是在【第2章节】@B位置创建的对象;SpringApplicationRunListeners实则是对SpringApplicationRunListener集合的封装,两者相差一个字母 s
  • @E:拿到beanFactory,一顿自定义操作
  • @F:注册BeanFactory后置处理器
  • @G:调用所有SpringApplicationRunListener子类的 contextLoaded方法,通知context已加载完毕
自动装配

以上内容只是梳理了spring-boot应用启动的大致流程,那么自动装配发生在什么阶段呢?这里可以看看之前我对spring-boot2自动装配梳理的文章: Springboot2 自动装配之spring-autoconfigure-metadata.properties和spring.factories(SPI机制核心)

同样spring-boot3自动装配也是看@SpringBootApplication这个注解,该注解是一个复合注解:

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

再看看EnableAutoConfiguration 这个注解:

@AutoConfigurationPackage
@Import(ImportAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}

被@Import的是一个AutoConfigurationImportSelector类和spring-boot2一样,但是实现细节不一样了,增加了从META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports里获取配置类,代码如下:

	protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {if (!isEnabled(annotationMetadata)) {return EMPTY_ENTRY;}AnnotationAttributes attributes = getAttributes(annotationMetadata);// @AList<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);configurations = removeDuplicates(configurations);Set<String> exclusions = getExclusions(annotationMetadata, attributes);checkExcludedClasses(configurations, exclusions);configurations.removeAll(exclusions);configurations = getConfigurationClassFilter().filter(configurations);fireAutoConfigurationImportEvents(configurations, exclusions);return new AutoConfigurationEntry(configurations, exclusions);}
  • @A 从META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports配置获取自动装配类(这是和spring-boot2差异比较大的一点)
  • spring-boot3并不兼容spring.factories配置自动装配类,这一点个人觉得很不友好,因为升级后原来自定义的装配类都要重新迁移一遍
  • 通过断点看出来,这里获取自动装配类的时机是由bean工厂后置处理器触发:
    在这里插入图片描述

其他自动装配流程和spring-boot2差不多,可以看看我之前文章。

建议

在看源码的时候有一些关键类需要注意一下DefaultBootstrapContext bootstrapContext是springboot提供的, ConfigurableApplicationContext context是spring-framework 提供的,这样就会更清晰一点;

over~~

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

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

相关文章

JVM面试题解,垃圾回收之“分代回收理论”剖析

一、什么是分代回收 我们会把堆内存中的对象间隔一段时间做一次GC&#xff08;即垃圾回收&#xff09;&#xff0c;但是堆内存很大一块&#xff0c;内存布局分为新生代和老年代、其对象的特点不一样&#xff0c;所以回收的策略也应该各不相同 对于“刚出生”的新对象&#xf…

“腾讯、钉钉、飞书” 会议开源平替,免费功能强大

在数字化时代&#xff0c;远程办公和线上协作越来越火。然而&#xff0c;市面上的视频会议工具要么贵得离谱&#xff0c;要么功能受限&#xff0c;甚至还有些在数据安全和隐私保护上让人不放心。 今天开源君给大家安利一个超棒的开源项目 - Jitsi Meet&#xff0c;这可是我在网…

CNN-GRU卷积门控循环单元时间序列预测(Matlab完整源码和数据)

CNN-GRU卷积门控循环单元时间序列预测&#xff08;Matlab完整源码和数据&#xff09; 目录 CNN-GRU卷积门控循环单元时间序列预测&#xff08;Matlab完整源码和数据&#xff09;预测效果基本介绍CNN-GRU卷积门控循环单元时间序列预测一、引言1.1、研究背景与意义1.2、研究现状1…

状态模式——C++实现

目录 1. 状态模式简介 2. 代码示例 3. 单例状态对象 4. 状态模式与策略模式的辨析 1. 状态模式简介 状态模式是一种行为型模式。 状态模式的定义&#xff1a;状态模式允许对象在内部状态改变时改变它的行为&#xff0c;对象看起来好像修改了它的类。 通俗的说就是一个对象…

大华相机DH-IPC-HFW3237M支持的ONVIF协议

使用libONVIF C库。 先发现相机。 配置 lib目录 包含 编译提示缺的文件&#xff0c;到libonvif里面拷贝过来。 改UDP端口 代码 使用msvc 2022的向导生成空项目&#xff0c;从项目的main示例拷贝过来。 CameraOnvif.h #pragma once#include <QObject> #include &l…

WIN11 UEFI漏洞被发现, 可以绕过安全启动机制

近日&#xff0c;一个新的UEFI漏洞被发现&#xff0c;可通过多个系统恢复工具传播&#xff0c;微软已经正式将该漏洞标记为追踪编号“CVE-2024-7344”。根据报告的说明&#xff0c;该漏洞能让攻击者绕过安全启动机制&#xff0c;并部署对操作系统隐形的引导工具包。 据TomsH…

Kyligence AI 数据智能体:首批亮相神州数码 DC·AI 生态创新中心!

近日&#xff0c;跬智信息&#xff08;Kyligence&#xff09;长期合作伙伴神州数码&#xff0c;其 DCAI 生态创新中心正式启幕。 作为首批生态伙伴&#xff0c;Kyligence AI 数据智能体也正式入驻&#xff0c;在这里首次亮相。 Kyligence 是国内最早推出 AI 用数产品的厂商&a…

GS论文阅读--Hard Gaussian Splatting

前言 本文也是对高斯点云的分布进行优化的&#xff0c;看&#xff01; 文章目录 前言1.背景介绍2.关键内容2.1 位置梯度驱动HGS2.2 渲染误差引导HGS 3.文章贡献 1.背景介绍 在训练过程中&#xff0c;它严重依赖于视图空间位置梯度的平均幅度来增长高斯以减少渲染损失。然而&…

消息队列篇--原理篇--Pulsar(Namespace,BookKeeper,类似Kafka甚至更好的消息队列)

Apache Pulusar是一个分布式、多租户、高性能的发布/订阅&#xff08;Pub/Sub&#xff09;消息系统&#xff0c;最初由Yahoo开发并开源。它结合了Kafka和传统消息队列的优点&#xff0c;提供高吞吐量、低延迟、强一致性和可扩展的消息传递能力&#xff0c;适用于大规模分布式系…

当 Facebook 窥探隐私:用户的数字权利如何捍卫?

随着社交平台的普及&#xff0c;Facebook 已经成为全球用户日常生活的一部分。然而&#xff0c;伴随而来的隐私问题也愈发严峻。近年来&#xff0c;Facebook 频频被曝出泄露用户数据、滥用个人信息等事件&#xff0c;令公众对其隐私保护措施产生质疑。在这个信息化时代&#xf…

OSCP - Proving Grounds - Quackerjack

主要知识点 端口转发 具体步骤 执行nmap扫描,开了好多端口&#xff0c;我先试验80和8081&#xff0c;看起来8081比较有趣 Nmap scan report for 192.168.51.57 Host is up (0.0011s latency). Not shown: 65527 filtered tcp ports (no-response) PORT STATE SERVICE …

Hive之加载csv格式数据到hive

场景&#xff1a; 今天接了一个需求&#xff0c;将测试环境的hive数据导入到正式环境中。但是不需要整个流程的迁移&#xff0c;只需要迁移ads表 解决方案&#xff1a; 拿到这个需求首先想到两个方案&#xff1a; 1、将数据通过insert into语句导出&#xff0c;然后运行脚本 …

PHP如何封装项目框架达到高可用、高性能、高并发

很多初创公司为了快速上线业务&#xff0c;开发时间由本来的6个月压缩到3个月甚至2个月。开发人员只能根据时间及业务需求去git上找现有的项目二次开发或者是一个空框架根据业务一点一点的去做&#xff0c;上述两种方案虽然也可以上线但是对于业务本身存在的问题也是很大的&…

探究 Facebook 隐私安全发展方向,未来走向何方?

随着社交媒体的普及&#xff0c;隐私和数据安全问题成为了全球关注的焦点。Facebook&#xff0c;作为全球最大的社交平台之一&#xff0c;其隐私安全问题尤其引人注目。近年来&#xff0c;随着用户数据泄露事件的不断发生&#xff0c;Facebook 不断调整其隐私政策&#xff0c;探…

【Linux】其他备选高级IO模型

其他高级 I/O 模型 以上基本介绍的都是同步IO相关知识点&#xff0c;即在同步I/O模型中&#xff0c;程序发起I/O操作后会等待I/O操作完成&#xff0c;即程序会被阻塞&#xff0c;直到I/O完成。整个I/O过程在同一个线程中进行&#xff0c;程序在等待期间不能执行其他任务。下面…

R语言学习笔记之开发环境配置

一、概要 整个安装过程及遇到的问题记录 操作步骤备注&#xff08;包含遇到的问题&#xff09;1下载安装R语言2下载安装RStudio3离线安装pacman提示需要安装Rtools4安装Rtoolspacman、tidyfst均离线安装完成5加载tidyfst报错 提示需要安装依赖&#xff0c;试错逐步下载并安装…

数据分析 six库

目录 起因 什么是six库 智能识别py2或3 ​编辑 起因 ModuleNotFoundError: No module named sklearn.externals.six sklearn.externals.six 模块在较新版本的 scikit-learn 中已经被移除。如果你在尝试使用这个模块时遇到了 ModuleNotFoundError: No module named sklear…

HTML5使用favicon.ico图标

目录 1. 使用favicon.ico图标 1. 使用favicon.ico图标 favicon.ico一般用于作为网站标志&#xff0c;它显示在浏览器的地址栏或者标签上 制作favicon图标 选择一个png转ico的在线网站&#xff0c;这里以https://www.bitbug.net/为例。上传图片&#xff0c;目标尺寸选择48x48&a…

Langchain+文心一言调用

import osfrom langchain_community.llms import QianfanLLMEndpointos.environ["QIANFAN_AK"] "" os.environ["QIANFAN_SK"] ""llm_wenxin QianfanLLMEndpoint()res llm_wenxin.invoke("中国国庆日是哪一天?") print(…

Linux系统下速通stm32的clion开发环境配置

陆陆续续搞这个已经很久了。 因为自己新电脑是linux系统无法使用keil&#xff0c;一开始想使用vscode里的eide但感觉不太好用&#xff1b;后面想直接使用cudeide但又不想妥协&#xff0c;想趁着这个机会把linux上的其他单片机开发配置也搞明白&#xff1b;而且非常想搞懂cmake…