Spring Boot启动流程详解

Spring Boot是一个基于Spring框架的快速开发工具,它可以帮助我们快速搭建一个可运行的Spring应用。本文将详细介绍Spring Boot的启动流程,帮助大家更好地理解Spring Boot的工作原理。

一、Spring Boot启动流程概述

Spring Boot的启动流程可以分为以下几个阶段:

  • 初始化配置

  • 创建应用程序上下文

  • 刷新上下文(启动核心)

  • 通知监听者-启动程序完成

public ConfigurableApplicationContext run(String... args) {long startTime = System.nanoTime();DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();ConfigurableApplicationContext context = null;this.configureHeadlessProperty();SpringApplicationRunListeners listeners = this.getRunListeners(args);listeners.starting(bootstrapContext, this.mainApplicationClass);try {// 获取args参数对象ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);// 读取Springboot配置文件并创建Environment对象// 这里创建的Environment对象实际为ConfigurableEnvironmentConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);this.configureIgnoreBeanInfo(environment);// 打印Banner图标Banner printedBanner = this.printBanner(environment);// 创建ApplicationContext应用行下文,即创建容器context = this.createApplicationContext();context.setApplicationStartup(this.applicationStartup);// 准备容器this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);// 初始化容器this.refreshContext(context);this.afterRefresh(context, applicationArguments);Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);if (this.logStartupInfo) {(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), timeTakenToStartup);}// 调用运行时监听器的started()方法// 该方法需要在应用程序启动后,CommandLineRunners和ApplicationRunners被调用前执行listeners.started(context, timeTakenToStartup);this.callRunners(context, applicationArguments);} catch (Throwable var12) {this.handleRunFailure(context, var12, listeners);throw new IllegalStateException(var12);}try {Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);listeners.ready(context, timeTakenToReady);return context;} catch (Throwable var11) {this.handleRunFailure(context, var11, (SpringApplicationRunListeners)null);throw new IllegalStateException(var11);}}

这段代码是Spring Boot启动时的核心方法,它的主要作用是创建和配置Spring应用程序的上下文。

首先,记录启动开始时间,并创建一个DefaultBootstrapContext对象和一个空的ConfigurableApplicationContext对象。然后,获取运行监听器并调用其starting()方法,表示应用程序正在启动。

接下来,创建一个ApplicationArguments对象,用于存储应用程序的命令行参数。然后,调用prepareEnvironment()方法准备环境,包括加载配置文件、扫描并注册Bean、初始化Spring容器等。

接着,打印应用程序的Banner信息,并创建一个新的ConfigurableApplicationContext对象。设置应用程序的启动类和启动配置,并调用prepareContext()方法准备上下文。

然后,调用refreshContext()方法刷新上下文,并调用afterRefresh()方法执行一些后续操作。计算应用程序启动所花费的时间,并在日志中记录启动信息。

最后,调用运行监听器的started()方法,表示应用程序已经启动。然后,调用callRunners()方法执行所有的CommandLineRunner和ApplicationRunner接口实现。

如果在启动过程中出现异常,会调用handleRunFailure()方法处理异常,并抛出一个IllegalStateException异常。

最后,调用运行监听器的ready()方法,表示应用程序已经准备好,并返回创建的ConfigurableApplicationContext对象。

二、详细解析

初始化配置

Spring Boot启动时,会首先加载配置文件(如application.properties或application.yml)中的配置信息。这些配置信息包括数据源、缓存、日志等相关信息。

 private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {ConfigurableEnvironment environment = this.getOrCreateEnvironment();this.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.");this.bindToSpringApplication(environment);if (!this.isCustomEnvironment) {EnvironmentConverter environmentConverter = new EnvironmentConverter(this.getClassLoader());environment = environmentConverter.convertEnvironmentIfNecessary(environment, this.deduceEnvironmentClass());}ConfigurationPropertySources.attach(environment);return environment;}

这段代码是一个名为prepareEnvironment的方法,它接收三个参数:listeners(SpringApplicationRunListeners类型)、bootstrapContext(DefaultBootstrapContext类型)和applicationArguments(ApplicationArguments类型)。该方法的主要作用是准备和配置Spring应用程序的环境。

首先,通过调用this.getOrCreateEnvironment()方法获取或创建一个ConfigurableEnvironment对象,并将其赋值给变量environment。

接下来,调用this.configureEnvironment(environment, applicationArguments.getSourceArgs())方法对环境进行配置,传入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.”)确保环境中不包含名为"spring.main.environment-prefix"的属性。

然后,调用this.bindToSpringApplication(environment)方法将环境绑定到Spring应用程序。

如果this.isCustomEnvironment为false,则创建一个EnvironmentConverter对象,并调用其convertEnvironmentIfNecessary(environment, this.deduceEnvironmentClass())方法根据需要转换环境。

最后,再次调用ConfigurationPropertySources.attach(environment)方法将环境附加到配置属性源,并将environment返回。

创建应用程序上下文

初始化和配置好后,开始创建应用程序上下文,createApplicationContext ,关键的工厂BeanFactory就是此处创建

   protected ConfigurableApplicationContext createApplicationContext() {return this.applicationContextFactory.create(this.webApplicationType);}

刷新上下文(启动核心)

工厂配置,bean处理器配置,类的扫描,解析,bean定义,bean类信息缓存,服务器创建,bean实例化,动态代理对象的创建等,

 private void refreshContext(ConfigurableApplicationContext context) {if (this.registerShutdownHook) {shutdownHook.registerApplicationContext(context);}this.refresh(context);}

该方法的主要作用是刷新Spring应用程序的上下文。

首先,如果this.registerShutdownHook为true,则调用shutdownHook.registerApplicationContext(context)方法将应用程序上下文注册到关闭钩子中。

然后,调用this.refresh(context)方法刷新上下文。

 public void refresh() throws BeansException, IllegalStateException {synchronized(this.startupShutdownMonitor) {StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");this.prepareRefresh();ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();this.prepareBeanFactory(beanFactory);try {this.postProcessBeanFactory(beanFactory);StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");//注册并实例化bean工厂处理器,并调用他们this.invokeBeanFactoryPostProcessors(beanFactory);//注册并实例化bean处理器this.registerBeanPostProcessors(beanFactory);beanPostProcess.end();this.initMessageSource();this.initApplicationEventMulticaster();//初始化一些与上下文有特别关系的bean对象(创建tomcat)this.onRefresh();this.registerListeners();// 实例化所有bean工厂缓存的bean对象(剩下的).this.finishBeanFactoryInitialization(beanFactory);//发布通知-通知上下文刷新完成(包括启动tomcat)this.finishRefresh();} catch (BeansException var10) {if (this.logger.isWarnEnabled()) {this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var10);}this.destroyBeans();this.cancelRefresh(var10);throw var10;} finally {this.resetCommonCaches();contextRefresh.end();}}}

该方法的主要作用是刷新Spring应用程序的上下文。

首先,通过同步块来确保线程安全。然后,创建一个名为contextRefresh的StartupStep对象,并调用this.prepareRefresh()方法准备刷新。接着,获取一个新的BeanFactory对象,并调用this.prepareBeanFactory(beanFactory)方法准备BeanFactory。

接下来,在try-catch语句中执行以下操作:

  • 调用this.postProcessBeanFactory(beanFactory)方法对BeanFactory进行后处理。
  • 创建一个名为beanPostProcess的StartupStep对象,并调用this.invokeBeanFactoryPostProcessors(beanFactory)方法执行BeanFactory的后处理器。
  • 调用this.registerBeanPostProcessors(beanFactory)方法注册Bean的后处理器。
  • 结束beanPostProcess的计时。
  • 调用this.initMessageSource()方法初始化消息源。
  • 调用this.initApplicationEventMulticaster()方法初始化应用程序事件多播器。
  • 调用this.onRefresh()方法执行刷新操作。
  • 调用this.registerListeners()方法注册监听器。
  • 调用this.finishBeanFactoryInitialization(beanFactory)方法完成BeanFactory的初始化。
  • 调用this.finishRefresh()方法完成刷新操作。

如果在执行过程中出现BeansException异常,会记录警告日志,销毁Beans,取消刷新操作,并抛出该异常。最后,在finally语句中重置缓存和计时器。

通知监听者-启动程序完成

发布通知监听器启动完成,监听器会根据事件类型做个性化操作

  void starting(ConfigurableBootstrapContext bootstrapContext, Class<?> mainApplicationClass) {this.doWithListeners("spring.boot.application.starting", (listener) -> {listener.starting(bootstrapContext);}, (step) -> {if (mainApplicationClass != null) {step.tag("mainApplicationClass", mainApplicationClass.getName());}});}void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {this.doWithListeners("spring.boot.application.environment-prepared", (listener) -> {listener.environmentPrepared(bootstrapContext, environment);});}void contextPrepared(ConfigurableApplicationContext context) {this.doWithListeners("spring.boot.application.context-prepared", (listener) -> {listener.contextPrepared(context);});}void contextLoaded(ConfigurableApplicationContext context) {this.doWithListeners("spring.boot.application.context-loaded", (listener) -> {listener.contextLoaded(context);});}void started(ConfigurableApplicationContext context, Duration timeTaken) {this.doWithListeners("spring.boot.application.started", (listener) -> {listener.started(context, timeTaken);});}void ready(ConfigurableApplicationContext context, Duration timeTaken) {this.doWithListeners("spring.boot.application.ready", (listener) -> {listener.ready(context, timeTaken);});}void failed(ConfigurableApplicationContext context, Throwable exception) {this.doWithListeners("spring.boot.application.failed", (listener) -> {this.callFailedListener(listener, context, exception);}, (step) -> {step.tag("exception", exception.getClass().toString());step.tag("message", exception.getMessage());});}private void callFailedListener(SpringApplicationRunListener listener, ConfigurableApplicationContext context, Throwable exception) {try {listener.failed(context, exception);} catch (Throwable var6) {if (exception == null) {ReflectionUtils.rethrowRuntimeException(var6);}if (this.log.isDebugEnabled()) {this.log.error("Error handling failed", var6);} else {String message = var6.getMessage();message = message != null ? message : "no error message";this.log.warn("Error handling failed (" + message + ")");}}}private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction) {this.doWithListeners(stepName, listenerAction, (Consumer)null);}private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction, Consumer<StartupStep> stepAction) {StartupStep step = this.applicationStartup.start(stepName);this.listeners.forEach(listenerAction);if (stepAction != null) {stepAction.accept(step);}step.end();}

SpringApplicationRunListeners是Spring Boot框架中的一个接口,用于监听应用程序的运行过程。它提供了一系列的回调方法,可以在应用程序的不同阶段执行自定义的逻辑。

以下是SpringApplicationRunListeners接口中定义的一些主要方法:

  • starting(): 在应用程序启动之前调用,可以执行一些初始化操作。
  • environmentPrepared(): 在环境准备完成后调用,可以获取到应用程序的环境信息。
  • contextPrepared(): 在上下文准备完成后调用,可以获取到应用程序的上下文对象。
  • contextLoaded(): 在上下文加载完成后调用,可以执行一些额外的配置操作。
  • started(): 在应用程序启动完成后调用,可以执行一些后续处理操作。
  • running(): 在应用程序运行期间持续调用,可以执行一些周期性的任务。
  • failed(): 在应用程序启动失败时调用,可以执行一些错误处理操作。
  • stopped(): 在应用程序停止后调用,可以执行一些清理操作。

总结

通过以上介绍,我们了解了Spring Boot的启动流程。Spring Boot通过自动化的配置和简化的部署流程,使得我们能够快速搭建一个可运行的Spring应用。希望本文能够帮助大家更好地理解Spring Boot的工作原理。

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

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

相关文章

排序算法——梳理总结

✨冒泡 ✨选择 ✨插入  ✨标准写法  &#x1f3ad;不同写法 ✨希尔排序——标准写法 ✨快排 ✨归并 ✨堆排 ✨冒泡 void Bubble(vector<int>& nums) {// 冒泡排序只能先确定最右边的结果&#xff0c;不能先确定最左边的结果for (int i 0; i < nums.size(); i){…

基于深度学习的交通标志检测识别系统(含UI界面、yolov8、Python代码、数据集)

项目介绍 项目中所用到的算法模型和数据集等信息如下&#xff1a; 算法模型&#xff1a;     yolov8 yolov8主要包含以下几种创新&#xff1a;         1. 添加注意力机制&#xff08;SE、CBAM等&#xff09;         2. 修改可变形卷积&#xff08;DySnake-主干c…

linux系统命令深入研究1——ls的参数

ls list命令有一些常用的参数&#xff0c;其中-a意为列出all全部文件&#xff08;包括隐藏文件&#xff09;&#xff0c;-l列出详细信息&#xff0c;-h以人类可阅读的方式列出文件大小 --full-time是列出详细时间信息&#xff0c;包括最后一次修改时间 -t是按时间排序&#xff…

课时57:数组实践_综合实践_数组案例

1.2.2 数组案例 学习目标 这一节&#xff0c;我们从 信息统计、服务管理、小结 三个方面来学习。 信息统计 需求 分别打印CPU 1min 5min 15min load负载值 命令提示&#xff1a;uptime信息显示&#xff1a;CPU 1 min平均负载为: 0.00CPU 5 min平均负载为: 0.01CPU 15 min平…

Git 内幕探索:从底层文件系统到历史编辑的全面指南

微信搜索“好朋友乐平”关注公众号。 1. Git 底层文件对象 #mermaid-svg-uTkvyr26fNmajZ3n {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-uTkvyr26fNmajZ3n .error-icon{fill:#552222;}#mermaid-svg-uTkvyr26fNmaj…

Spark实战-基于Spark日志清洗与数据统计以及Zeppelin使用

Saprk-日志实战 一、用户行为日志 1.概念 用户每次访问网站时所有的行为日志(访问、浏览、搜索、点击)用户行为轨迹&#xff0c;流量日志2.原因 分析日志&#xff1a;网站页面访问量网站的粘性推荐3.生产渠道 (1)Nginx(2)Ajax4.日志内容 日志数据内容&#xff1a;1.访问的…

每天学习一个Linux命令之chown

每天学习一个Linux命令之chown 在Linux系统中&#xff0c;chown命令用于更改文件或目录的所有者。通常情况下&#xff0c;只有超级用户root和文件所有者本人可以使用chown命令。在这篇博客中&#xff0c;我们将详细介绍chown命令的用法以及所有可用的选项。 1. 命令格式 bas…

【动态规划】完全背包

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;折纸花满衣 &#x1f3e0;个人专栏&#xff1a;题目解析 &#x1f30e;推荐文章&#xff1a;【LeetCode】winter vacation training 目录 &#x1f449;&#x1f3fb;完全背包 &#x1f449;&#x1f3fb;…

政安晨:【深度学习处理实践】(二)—— 最大汇聚运算

最大汇聚运算&#xff08;Max Pooling Operation&#xff09;是深度学习领域卷积神经网络常用的一种汇聚运算方式。在卷积神经网络中&#xff0c;经过一系列卷积层和激活函数层后&#xff0c;数据在空间尺寸上逐渐减小&#xff0c;特征图的深度也逐渐增加。为了降低数据尺寸并提…

微信小程序(五十三)修改用户头像与昵称

注释很详细&#xff0c;直接上代码 上一篇 新增内容&#xff1a; 1.外界面个人资料基本模块 2.资料修改界面同步问题实现&#xff08;细节挺多&#xff0c;考虑了后期转服务器端的方便之处&#xff09; 源码&#xff1a; app.json {"window": {},"usingCompone…

算法打卡day11|栈与队列篇03|Leetcode 239. 滑动窗口最大值、347.前 K 个高频元素

小顶堆和大顶堆 小顶堆&#xff08;Min Heap&#xff09;和大顶堆&#xff08;Max Heap&#xff09;是两种特殊的完全二叉树&#xff0c;它们遵循特定的堆属性&#xff0c;即父节点的值总是小于或等于&#xff08;小顶堆&#xff09;或者大于或等于&#xff08;大顶堆&#xf…

Spring AOP相关注解及执行顺序

Aspect&#xff08;切面&#xff09;&#xff1a;用于标识一个类是切面的注解。通常与其他通知注解一起使用&#xff0c;定义切面类。 Pointcut&#xff08;切点&#xff09;&#xff1a; 注解来定义切点&#xff0c;它用于描述哪些连接点将会被通知所通知。 连接点&#xff…

Linux minfo命令教程:详解MS-DOS文件系统参数查看命令(附实例详解和注意事项)

Linux minfo命令介绍 minfo命令用于显示MS-DOS文件系统的各项参数&#xff0c;如磁区数、磁头数和柱面数等。它还可以打印一个mformat命令行&#xff0c;该命令行可以用于在其他媒体上创建类似的MS-DOS文件系统。 Linux minfo命令适用的Linux版本 minfo命令在大多数Linux发行…

算法D39 | 动态规划2 | 62.不同路径 63. 不同路径 II

今天开始逐渐有 dp的感觉了&#xff0c;题目不多&#xff0c;就两个 不同路径&#xff0c;可以好好研究一下 62.不同路径 本题大家掌握动态规划的方法就可以。 数论方法 有点非主流&#xff0c;很难想到。 代码随想录 视频讲解&#xff1a;动态规划中如何初始化很重要&#x…

Latex公式太长换行标号

Latex中公式太长换行&#xff0c;且编号&#xff0c;可以采用align&#xff0c;不编号行公式用\nonumber&#xff0c;示例如下&#xff1a; \begin{align}\nonumber %第1行公式不编号&abababababababa\\&cdm %第2行公式编号 \end{align}效果如下 原文件链接 公式不…

清除Mac OS上Xcode占用的空间

最近自己的Mac OS存储空间严重不足&#xff0c;想了一下&#xff0c;大概是从安装 Xcode 之后出现&#xff0c;在系统下通过 du 命令分析各目录大小&#xff0c;发现大概下面几个目录占用空间比较大&#xff0c;所以针对这几个名目录作了一下清理&#xff0c;释放了几十个G的空…

Leetcode 268 火星词典

题目描述&#xff1a; 现有一种使用英语字母的火星语言&#xff0c;这门语言的字母顺序与英语顺序不同。 给你一个字符串列表 words &#xff0c;作为这门语言的词典&#xff0c;words 中的字符串已经 按这门新语言的字母顺序进行了排序 。 请你根据该词典还原出此语言中已知…

<网络安全>《65 微课堂<第6课 网间文件安全交换系统>》

1 5中文件传输方式 1.1 通过U盘、硬盘、光盘等硬件介质传输 优势是成本低。 劣势是1、无法对这个行为过程进行监控管理。2、大文件较大、高频率交换时需要专人负责。3、由于这个过程并没有杀毒、内容检测等环节&#xff0c;因此具有较大的安全风险。 1.2 FTP、SFTP等跨网传输…

WordPress建站入门教程:如何上传安装WordPress主题?

我们成功搭建WordPress网站后&#xff0c;默认使用的是自带的最新主题&#xff0c;但是这个是国外主题&#xff0c;可能会引用一些国外的资源文件&#xff0c;所以为了让我们的WordPress网站访问速度更快&#xff0c;强烈建议大家使用国产优秀的WordPress主题。 今天boke112百…

【C语言】球球大作战

前言&#xff1a; 这款简易版的球球大作战是一款单人游戏&#xff0c;玩家需要控制一个小球在地图上移动&#xff0c;吞噬其他小球来增大自己的体积。本游戏使用C语言和easyx图形库编写&#xff0c;旨在帮助初学者了解游戏开发的基本概念和技巧。 在开始编写代码之前&#xf…