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

Spring Boot 深入剖析:SpringBoot的启动流程

本章我们来讲一下整个SpringBoot的启动流程,在程序启动的时候其实是运行的main方法当做程序的入口来实现的。而main方法则是调用的run方法来实现的。因此整个SpringBoot的启动流程的逻辑都在run方法中的。

run方法的源码分析

	public ConfigurableApplicationContext run(String... args) {long startTime = System.nanoTime();DefaultBootstrapContext bootstrapContext = createBootstrapContext();ConfigurableApplicationContext context = null;configureHeadlessProperty();SpringApplicationRunListeners listeners = getRunListeners(args);listeners.starting(bootstrapContext, this.mainApplicationClass);try {ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);Banner printedBanner = printBanner(environment);context = createApplicationContext();context.setApplicationStartup(this.applicationStartup);prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);refreshContext(context);afterRefresh(context, applicationArguments);Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);}listeners.started(context, timeTakenToStartup);callRunners(context, applicationArguments);}catch (Throwable ex) {if (ex instanceof AbandonedRunException) {throw ex;}handleRunFailure(context, ex, listeners);throw new IllegalStateException(ex);}try {if (context.isRunning()) {Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);listeners.ready(context, timeTakenToReady);}}catch (Throwable ex) {if (ex instanceof AbandonedRunException) {throw ex;}handleRunFailure(context, ex, null);throw new IllegalStateException(ex);}return context;}

上述代码则是run方法的主要核心代码,下面我们来对其进行详细分析。

DefaultBootstrapContext bootstrapContext = createBootstrapContext();private DefaultBootstrapContext createBootstrapContext() {DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();this.bootstrapRegistryInitializers.forEach((initializer) -> initializer.initialize(bootstrapContext));return bootstrapContext;
}

首先则是调用了createBootstrapContext这个方法,而createBootstrapContext这个方法的源码我们可以看到当前是new了一个DefaultBootstrapContext 类(这个类之前已经讲过本质上是springBoot启动过程中的辅助工具类),随后是遍历当前SpringApplication所含的bootstrapRegistryInitializers集合,一个一个调用bootstrapRegistryInitializer来对其DefaultBootstrapContext对象进行初始化操作。

configureHeadlessProperty();private void configureHeadlessProperty() {System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
}

随后则是调用了configureHeadlessProperty方法,该方法的作用则是设置一个系统属性。由源码可以看到当前,设置了一个SYSTEM_PROPERTY_JAVA_AWT_HEADLESS的属性,其中设置getProperty则是查看是否预先已经有当前属性值,如果有则用当前有的,没有则使用getProperty第三个参数值(用作默认值)

SpringApplicationRunListeners listeners = getRunListeners(args);private SpringApplicationRunListeners getRunListeners(String[] args) {ArgumentResolver argumentResolver = ArgumentResolver.of(SpringApplication.class, this);argumentResolver = argumentResolver.and(String[].class, args);List<SpringApplicationRunListener> listeners = getSpringFactoriesInstances(SpringApplicationRunListener.class,argumentResolver);SpringApplicationHook hook = applicationHook.get();SpringApplicationRunListener hookListener = (hook != null) ? hook.getRunListener(this) : null;if (hookListener != null) {listeners = new ArrayList<>(listeners);listeners.add(hookListener);}return new SpringApplicationRunListeners(logger, listeners, this.applicationStartup);}

第三个方法则是调用了getRunListeners方法目的是获取并初始化SpringApplicationRunListeners类,下面我们来看看源码解析。在源码中我们可以看到首先调用getSpringFactoriesInstances方法来实例化得到一个SpringApplicationRunListener的集合。随后处理 Spring Boot 的应用钩子机制:

  • applicationHook 是一个 ThreadLocal<SpringApplicationHook>,用于支持测试或其他特殊场景

  • 如果存在钩子,通过 hook.getRunListener(this) 获取一个额外的 SpringApplicationRunListener

  • 将这个额外的监听器添加到监听器列表中

applicationHook 是 Spring Boot 提供的一个基于 ThreadLocal 的钩子机制,它允许在运行时动态地向 Spring Boot 应用添加额外的功能,包括额外的 SpringApplicationRunListener

  1. 是用于添加用户自定义的监听器(通过继承实现方法来实现)

  2. 主要面向框架开发者和高级用户

  3. 提供了运行时动态扩展的能力

  4. 特别适用于测试框架和其他需要环境特定行为的场景

  5. 与传统的 spring.factories 机制互补,提供了更大的灵活性

    方式使用场景优势劣势
    spring.factories常规扩展,所有环境都需要的监听器声明式,自动发现需要修改配置文件
    applicationHook运行时动态扩展,环境特定的监听器编程式,灵活控制需要手动设置

最后则是new一个SpringApplicationRunListeners,并且将得到的SpringApplicationRunListener当做构造方法的参数传递进去实现初始化的操作。

listeners.starting(bootstrapContext, this.mainApplicationClass);

随后则是使用listeners来发布了一个事件对应的事件监听器收到后则会做出处理。

ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {// Create and configure the environmentConfigurableEnvironment environment = getOrCreateEnvironment();configureEnvironment(environment, applicationArguments.getSourceArgs());ConfigurationPropertySources.attach(environment);listeners.environmentPrepared(bootstrapContext, 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;
}

之后则是将args封装为一个applicationArguments 对象,随后调用了prepareEnvironment用来进行配置信息的加载。对于prepareEnvironment方法的源码,首先是直接初始化了一个ConfigurableEnvironment对象,随后则是将main函数的args封装的对象设置到environment对象中去,随后attach方法则是将 Spring Boot 的高级配置属性处理机制附加到environment中。然后则是通过SpringApplicationRunListeners 来发布事件机制,随后则是将environment与当前的SpringApplication进行绑定。之后呢则是查看当前environment环境用户是否自定义了如果有则进行切换。最后再次调用attach方法然后返回environment。

两次调用 ConfigurationPropertySources.attach() 不是重复劳动,而是精心设计的:

  1. 第一次调用:为配置文件加载阶段提供配置属性支持

  2. 第二次调用:为应用运行阶段提供配置属性支持,并处理环境可能被替换的情况

Banner printedBanner = printBanner(environment);
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);

这个printBanner是 Spring Boot 启动过程中负责处理和控制应用启动横幅(Banner)显示的核心语句。也就是启动时候打印Spring的一个横幅文字。然后则创建了ApplicationContext这个对象,随后使用了setApplicationStartup方法,它负责将应用启动指标收集功能传递给 Spring 应用上下文。

ApplicationStartup 是 Spring Framework 5.3 引入的接口,用于收集应用启动过程中的性能指标数据。它提供了一种标准化的方式来记录启动过程中各个步骤的耗时和详细信息。

prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,ApplicationArguments applicationArguments, Banner printedBanner) {context.setEnvironment(environment);postProcessApplicationContext(context);addAotGeneratedInitializerIfNecessary(this.initializers);applyInitializers(context);listeners.contextPrepared(context);bootstrapContext.close(context);if (this.logStartupInfo) {logStartupInfo(context.getParent() == null);logStartupProfileInfo(context);}// Add boot specific singleton beansConfigurableListableBeanFactory beanFactory = context.getBeanFactory();beanFactory.registerSingleton("springApplicationArguments", applicationArguments);if (printedBanner != null) {beanFactory.registerSingleton("springBootBanner", printedBanner);}if (beanFactory instanceof AbstractAutowireCapableBeanFactory autowireCapableBeanFactory) {autowireCapableBeanFactory.setAllowCircularReferences(this.allowCircularReferences);if (beanFactory instanceof DefaultListableBeanFactory listableBeanFactory) {listableBeanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);}}if (this.lazyInitialization) {context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());}context.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]));}listeners.contextLoaded(context);}

随后的三个方法则是初始化ApplicationContext,以及正式启动ApplicationContext和启动ApplicationContext之后执行的业务方法。其中正式启动ApplicationContext方法本章不会讲解,后续专门出一章来进行讲解。首先我们看看初始化ApplicationContext的方法源码,第一步则是将environment对象放到ApplicationContext当中,随后的这个postProcessApplicationContext方法的作用则是在应用上下文创建后但尚未刷新之前,对其进行额外的配置和定制化处理。同时这个方法也是一个模版方法可以交给子类去重写方法。随后则是调用了ApplicationContextInitializer的initialize方法来实现对ApplicationContext的初始化配置,当初始化完成之后则采用listener发布当前事件,发布完成之后则关闭当前bootstrapContext。

随后则是注册关键组件:将启动参数和 Banner 注册为 Bean(加载特定的单例bean),配置容器行为:设置循环引用和 Bean 定义覆盖策略,启用高级特性:支持延迟初始化和属性源排序(加载),加载用户配置:处理主配置类和组件扫描,发布事件通知:告知监听器上下文已加载完成(listeners发布事件)。

而后续的afterRefresh则是一个模版方法用来留给子类去执行,当容器刷新完毕之后则执行当前的方法

 listeners.started(context, timeTakenToStartup);this.callRunners(context, applicationArguments);

最后则是通过SpringApplicationRunListeners来实现发布事件并且callRunners方法的主要作用是查找并执行所有实现了 ApplicationRunner 和 CommandLineRunner 接口的 Bean,为开发者提供了一个在应用完全启动后执行自定义初始化代码的标准方式。


文章转载自:

http://KrK7mw3X.hnmbq.cn
http://9RjFv24G.hnmbq.cn
http://yv7mKMSl.hnmbq.cn
http://lugQt74S.hnmbq.cn
http://0ZYyigex.hnmbq.cn
http://T82SkKk1.hnmbq.cn
http://OMoBA3dE.hnmbq.cn
http://XsQwgCuq.hnmbq.cn
http://Hs6IuoZa.hnmbq.cn
http://rdtUbD6M.hnmbq.cn
http://9AnCwyms.hnmbq.cn
http://bzMMoZzE.hnmbq.cn
http://YL21LDW3.hnmbq.cn
http://KLBGXMNj.hnmbq.cn
http://WXUx95yY.hnmbq.cn
http://hGkQ5XhM.hnmbq.cn
http://voOX3ZjP.hnmbq.cn
http://PsdAP9b8.hnmbq.cn
http://gV2tB5oI.hnmbq.cn
http://GuYE1vtv.hnmbq.cn
http://Afierc5T.hnmbq.cn
http://wZkexhQv.hnmbq.cn
http://UrRO41ro.hnmbq.cn
http://BK9KSgYd.hnmbq.cn
http://wzjY19d4.hnmbq.cn
http://aetN2XWy.hnmbq.cn
http://JxtcPRLC.hnmbq.cn
http://SFKaasTZ.hnmbq.cn
http://N9jvABhI.hnmbq.cn
http://3ey26SPa.hnmbq.cn
http://www.dtcms.com/a/387307.html

相关文章:

  • 机器人工具标定-记录一下-待验证(没数据)
  • 1.0 Labview中事件结构在while循环中使用注意事项(超时时间)
  • 微服务通信
  • 重定向、命令行判断、管道、正则三剑客
  • 破垄断!东土科技与海光信息联合发布全国产化工控系统,筑牢工业安全新底座
  • 一场史诗级的冒险——Docker命令大航海!
  • 基于 Node.js 的后端框架:NestJS 和 Express(二)
  • 大数据时代时序数据库选型指南:为何Apache IoTDB成优选——从技术架构与行业实践深度剖析
  • HBase 实战:3 步掌握基于 Rowkey 的数据更新技巧
  • 【Android】Jetpack Media3 播放音频文件
  • 算法 --- 队列 + 宽搜(BFS)
  • 苹果手机怎么导出App数据目录,iOS文件管理、应用沙盒访问、日志缓存导出与性能调试实战(uni-app开发者指南)
  • Java 设计模式——策略模式:从 3 种写法到 SpringBoot 进阶
  • JVM:性能调优的理解
  • AR眼镜在巡检业务中的软件架构设计|阿法龙XR云平台
  • 活动预告 | Paraverse × Unity:Unity云XR串流——突破设备与平台限制
  • 第十四届蓝桥杯青少组C++选拔赛[2022.12.18]第二部分编程题(5、猴子拿桃)
  • 二维码辅助回桩之二维码识别
  • Mojo vs Python vs Rust,2025年搞AI,怎么学
  • 从软件工程角度谈企业管理
  • 【C语言】C 语言自定义类型:联合与枚举的基础解析
  • 模型部署:(五)安卓端部署Yolov8关键点检测项目全流程记录
  • 在业务应用中集成 go-commons,实现应用+系统双指标监控
  • ESP32-C3四种工作模式
  • ReactNative中实现可拖拽的温度计组件
  • react snippets
  • 基于Matlab高低频混合建模的大气湍流相位屏生成算法
  • 2025年8月SCI-袋鼠逃生优化算法Kangaroo Escape Optimizer-附Matlab免费代码
  • Node.js 创建 TCP 服务
  • 关于鸿蒙配置HMRouter的问题,比如白屏等