从零开始解剖Spring Boot启动流程:一个Java小白的奇幻冒险之旅

大家好呀!今天我们要一起探索一个神奇的话题——Spring Boot的启动流程。我知道很多小伙伴一听到"启动流程"四个字就开始头疼,别担心!我会用最通俗易懂的方式,带你从main()方法开始,一步步揭开Spring Boot的神秘面纱,直到内嵌Tomcat欢快地跑起来~ 🏃‍♂️

准备好了吗?让我们开始这段奇妙的旅程吧!🚀

第一章:一切的开始——main()方法

1.1 那个我们熟悉的入口

每个Spring Boot项目都有一个main()方法,它就像是我们家的前门钥匙🔑,没有它,我们连门都进不去!

@SpringBootApplication
public class MyApplication {public static void main(String[] args) {SpringApplication.run(MyApplication.class, args);}
}

看到这个@SpringBootApplication注解了吗?它其实是个"套娃"注解,里面包含了三个重要注解:

  • @SpringBootConfiguration:标识这是个配置类
  • @EnableAutoConfiguration:开启自动配置魔法 ✨
  • @ComponentScan:告诉Spring去哪里找组件

1.2 SpringApplication.run() 做了什么?

当我们调用SpringApplication.run()时,背后发生了很多事情:

  1. 创建SpringApplication实例:就像组装一台新电脑 💻
  2. 运行SpringApplication:按下开机键!
// 伪代码简化版
public static ConfigurableApplicationContext run(Class primarySource, String... args) {// 1. 创建SpringApplication实例SpringApplication application = new SpringApplication(primarySource);// 2. 运行!return application.run(args);
}

第二章:SpringApplication的构造过程 �

2.1 确定应用类型

Spring Boot会先判断我们是什么类型的应用:

  • Web应用(Servlet):比如用Tomcat
  • Reactive Web应用:比如用Netty
  • 非Web应用:普通的Java应用
// 判断逻辑大致是这样的
private WebApplicationType deduceWebApplicationType() {if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", null) {return WebApplicationType.REACTIVE;}if (ClassUtils.isPresent("javax.servlet.Servlet", null)) {return WebApplicationType.SERVLET;}return WebApplicationType.NONE;
}

2.2 加载初始化器(Initializers)

初始化器就像是Spring Boot的"小助手"们,它们会在应用启动的不同阶段帮忙做一些准备工作。Spring Boot会从META-INF/spring.factories文件中加载这些初始化器。

# 示例 spring.factories 内容
org.springframework.context.ApplicationContextInitializer=\
com.example.MyInitializer,\
com.example.AnotherInitializer

2.3 加载监听器(Listeners)

监听器就像是一群"小耳朵"👂,它们会监听Spring Boot启动过程中的各种事件,比如:

  • ApplicationStartingEvent:应用刚开始启动
  • ApplicationEnvironmentPreparedEvent:环境准备好了
  • ApplicationPreparedEvent:应用上下文准备好了
  • ApplicationStartedEvent:应用启动完成
  • ApplicationReadyEvent:应用准备就绪

第三章:run()方法的奇幻旅程 🎢

现在,我们来到了最精彩的部分——run()方法的执行过程!让我们一步步来看:

3.1 第一步:启动计时器 ⏱️

Spring Boot一启动就会打开秒表,记录启动耗时:

StopWatch stopWatch = new StopWatch();
stopWatch.start();

3.2 第二步:准备环境 🌍

Spring Boot会准备运行环境,这包括:

  1. 创建环境对象(根据应用类型不同创建不同的环境)
  2. 配置环境(读取配置文件,如application.properties/yml)
  3. 发布ApplicationEnvironmentPreparedEvent事件
ConfigurableEnvironment environment = prepareEnvironment(...);

3.3 第三步:创建应用上下文 🏗️

根据应用类型创建不同的应用上下文:

  • Web应用:AnnotationConfigServletWebServerApplicationContext
  • Reactive Web应用:AnnotationConfigReactiveWebServerApplicationContext
  • 非Web应用:AnnotationConfigApplicationContext
context = createApplicationContext();

3.4 第四步:准备上下文 �

这一步会做很多重要工作:

  1. 准备环境
  2. 后置处理Bean定义
  3. 应用所有初始化器
  4. 发布ApplicationContextInitializedEvent事件
  5. 注册Spring Bean
prepareContext(context, environment, listeners, applicationArguments, printedBanner);

3.5 第五步:刷新上下文 🔄

这是整个启动过程中最复杂的一步!refresh()方法做了很多事情:

refreshContext(context);

让我们深入看看refresh()方法都做了什么:

3.5.1 准备刷新
  • 记录启动时间
  • 初始化占位符解析器
  • 验证必要的属性
3.5.2 获取BeanFactory
  • 创建DefaultListableBeanFactory
  • 加载Bean定义
3.5.3 准备BeanFactory
  • 设置类加载器
  • 添加Bean后置处理器
  • 注册环境Bean
3.5.4 执行BeanFactory后置处理器
  • 执行BeanFactoryPostProcessor的实现类
  • 这里会处理自动配置类(@EnableAutoConfiguration的核心)
3.5.5 注册Bean后置处理器
  • 注册各种BeanPostProcessor
  • 这些处理器会在Bean创建前后执行一些逻辑
3.5.6 初始化消息源
  • 国际化相关
3.5.7 初始化事件广播器
  • 用于发布应用事件
3.5.8 初始化其他特殊Bean
  • 比如Tomcat、Jetty等Web服务器
3.5.9 完成BeanFactory初始化
  • 初始化所有剩余的单例Bean
3.5.10 完成刷新
  • 发布ContextRefreshedEvent事件
  • 启动Web服务器(比如Tomcat)

3.6 第六步:收尾工作 🎬

启动完成后:

  1. 停止计时器
  2. 发布ApplicationStartedEventApplicationReadyEvent事件
  3. 返回应用上下文
afterRefresh(context, applicationArguments);
stopWatch.stop();

第四章:内嵌Tomcat的启动奥秘 🐱

现在,让我们重点看看内嵌Tomcat是如何启动的!这是很多小伙伴最感兴趣的部分~

4.1 Tomcat在哪里被创建?

refresh()方法的"初始化其他特殊Bean"阶段,Spring Boot会创建Web服务器。对于Tomcat来说,关键类是这个:

org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory

4.2 Tomcat启动流程

  1. 创建Tomcat实例

    Tomcat tomcat = new Tomcat();
    
  2. 配置基础信息

    • 设置端口(默认8080)
    • 设置上下文路径(默认"/")
    • 配置连接器等
  3. 创建Web应用上下文

    Context context = tomcat.addContext("", docBase);
    
  4. 配置Servlet容器

    • 添加DispatcherServlet
    • 配置过滤器等
  5. 启动Tomcat

    tomcat.start();
    

4.3 内嵌Tomcat vs 传统Tomcat

特点内嵌Tomcat传统Tomcat
启动方式通过Java代码启动通过startup.sh/bat脚本启动
部署方式打包成可执行JAR打包成WAR部署到webapps目录
配置方式通过application.properties配置通过server.xml配置
类加载器使用应用的类加载器使用Tomcat的类加载器
版本控制由Spring Boot管理版本需要单独安装和管理

4.4 为什么选择内嵌服务器?

  1. 简化部署:只需要一个JAR文件,不需要额外安装Tomcat
  2. 版本统一:Spring Boot管理Tomcat版本,避免冲突
  3. 快速启动:内嵌服务器启动更快
  4. 微服务友好:适合云原生和容器化部署
  5. 配置简单:通过配置文件即可完成大部分配置

第五章:自动配置的魔法 ✨

Spring Boot最强大的特性之一就是自动配置,让我们看看它是如何工作的!

5.1 @EnableAutoConfiguration 的秘密

这个注解会导入AutoConfigurationImportSelector,它会从META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中加载自动配置类。

5.2 条件注解的妙用

自动配置类通常使用各种条件注解来决定是否生效:

  • @ConditionalOnClass:当类路径下有指定类时生效
  • @ConditionalOnMissingBean:当容器中没有指定Bean时生效
  • @ConditionalOnProperty:当配置属性满足条件时生效

例如,Tomcat的自动配置类:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
public class EmbeddedTomcatAutoConfiguration {// 配置代码
}

5.3 自动配置的执行顺序

  1. 加载所有自动配置类
  2. 过滤掉不满足条件的配置类
  3. 按特定顺序应用剩余配置类
  4. 创建和配置各种Bean

第六章:Spring Boot启动流程图解 🗺️

为了帮助大家更好地理解,我画了一个简化的启动流程图:

main()│├─ 创建SpringApplication实例│   ├─ 确定应用类型│   ├─ 加载初始化器│   └─ 加载监听器│└─ 执行run()方法├─ 启动计时器├─ 准备环境├─ 创建应用上下文├─ 准备上下文├─ 刷新上下文 (核心!)│   ├─ 准备刷新│   ├─ 获取BeanFactory│   ├─ 准备BeanFactory│   ├─ 执行BeanFactory后置处理器│   ├─ 注册Bean后置处理器│   ├─ 初始化消息源│   ├─ 初始化事件广播器│   ├─ 初始化特殊Bean (如Tomcat)│   ├─ 完成BeanFactory初始化│   └─ 完成刷新├─ 收尾工作└─ 返回应用上下文

第七章:常见问题解答 ❓

7.1 为什么我的Spring Boot应用启动很慢?

可能原因:

  1. 类路径下JAR太多
  2. 自动配置类太多
  3. 数据库连接等初始化耗时
  4. 大量Bean需要初始化

解决方案:

  • 使用@Lazy延迟初始化
  • 排除不必要的自动配置
  • 减少启动时扫描的包路径

7.2 如何自定义内嵌Tomcat配置?

在application.properties中:

server.port=9090
server.tomcat.max-threads=200
server.tomcat.connection-timeout=5000

或者通过代码:

@Bean
public WebServerFactoryCustomizer tomcatCustomizer() {return factory -> factory.addConnectorCustomizers(connector -> {connector.setPort(9090);connector.setProperty("maxThreads", "200");});
}

7.3 如何查看自动配置的过程?

启动时添加debug参数:

java -jar myapp.jar --debug

或者在application.properties中:

debug=true

第八章:启动优化小技巧 ⚡

8.1 组件扫描优化

默认情况下,Spring Boot会扫描主类所在包及其子包。如果项目很大,可以明确指定扫描路径:

@ComponentScan("com.myapp.package1", "com.myapp.package2")

8.2 延迟初始化

Spring Boot 2.2+支持全局延迟初始化:

spring.main.lazy-initialization=true

8.3 排除自动配置

如果知道某些自动配置不需要,可以排除它们:

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})

8.4 使用Spring Boot DevTools

在开发环境中,添加DevTools可以加快重启速度:

org.springframework.bootspring-boot-devtoolsruntime

第九章:总结与展望 🏁

哇!我们终于走完了Spring Boot的整个启动流程!让我们回顾一下重点:

  1. main()方法是入口,调用SpringApplication.run()
  2. SpringApplication初始化时会确定应用类型、加载初始化和监听器
  3. run()方法是核心,完成了环境准备、上下文创建和刷新
  4. 刷新上下文是最复杂的部分,完成了Bean工厂准备、自动配置、Bean注册等
  5. 内嵌Tomcat在刷新阶段被创建和启动
  6. 自动配置通过条件注解智能地配置应用

Spring Boot的启动过程就像是一个精密的瑞士手表⌚,各个部件协同工作,最终让我们的应用顺利运行。理解这个过程不仅能帮助我们更好地使用Spring Boot,还能在遇到问题时快速定位原因。

希望这篇文章能帮助你理解Spring Boot的启动机制!如果觉得有用,别忘了点赞收藏哦~ 😊

下次见!👋

推荐阅读文章

  • 由 Spring 静态注入引发的一个线上T0级别事故(真的以后得避坑)

  • 如何理解 HTTP 是无状态的,以及它与 Cookie 和 Session 之间的联系

  • HTTP、HTTPS、Cookie 和 Session 之间的关系

  • 什么是 Cookie?简单介绍与使用方法

  • 什么是 Session?如何应用?

  • 使用 Spring 框架构建 MVC 应用程序:初学者教程

  • 有缺陷的 Java 代码:Java 开发人员最常犯的 10 大错误

  • 如何理解应用 Java 多线程与并发编程?

  • 把握Java泛型的艺术:协变、逆变与不可变性一网打尽

  • Java Spring 中常用的 @PostConstruct 注解使用总结

  • 如何理解线程安全这个概念?

  • 理解 Java 桥接方法

  • Spring 整合嵌入式 Tomcat 容器

  • Tomcat 如何加载 SpringMVC 组件

  • “在什么情况下类需要实现 Serializable,什么情况下又不需要(一)?”

  • “避免序列化灾难:掌握实现 Serializable 的真相!(二)”

  • 如何自定义一个自己的 Spring Boot Starter 组件(从入门到实践)

  • 解密 Redis:如何通过 IO 多路复用征服高并发挑战!

  • 线程 vs 虚拟线程:深入理解及区别

  • 深度解读 JDK 8、JDK 11、JDK 17 和 JDK 21 的区别

  • 10大程序员提升代码优雅度的必杀技,瞬间让你成为团队宠儿!

  • “打破重复代码的魔咒:使用 Function 接口在 Java 8 中实现优雅重构!”

  • Java 中消除 If-else 技巧总结

  • 线程池的核心参数配置(仅供参考)

  • 【人工智能】聊聊Transformer,深度学习的一股清流(13)

  • Java 枚举的几个常用技巧,你可以试着用用

  • 由 Spring 静态注入引发的一个线上T0级别事故(真的以后得避坑)

  • 如何理解 HTTP 是无状态的,以及它与 Cookie 和 Session 之间的联系

  • HTTP、HTTPS、Cookie 和 Session 之间的关系

  • 使用 Spring 框架构建 MVC 应用程序:初学者教程

  • 有缺陷的 Java 代码:Java 开发人员最常犯的 10 大错误

  • Java Spring 中常用的 @PostConstruct 注解使用总结

  • 线程 vs 虚拟线程:深入理解及区别

  • 深度解读 JDK 8、JDK 11、JDK 17 和 JDK 21 的区别

  • 10大程序员提升代码优雅度的必杀技,瞬间让你成为团队宠儿!

  • 探索 Lombok 的 @Builder 和 @SuperBuilder:避坑指南(一)

  • 为什么用了 @Builder 反而报错?深入理解 Lombok 的“暗坑”与解决方案(二)

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

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

相关文章

下载HBuilder X,使用uniapp编写微信小程序

到官网下载HBuilder X 地址:HBuilderX-高效极客技巧 下载完成后解压 打开解压后的文件夹找到HBuilderX.exe 打开显示更多,发送到桌面快捷方式 到桌面上启动HBuilderX.exe启动应用 在工具点击插件安装 选择安装Vue3编译器 点击新建创建Vue3项目 编写项目…

详解与HTTP服务器相关操作

HTTP 服务器是一种遵循超文本传输协议(HTTP)的服务器,用于在网络上传输和处理网页及其他相关资源。以下是关于它的详细介绍: 工作原理 HTTP 服务器监听指定端口(通常是 80 端口用于 HTTP,443 端口用于 HT…

2. ubuntu20.04 和VS Code实现 ros的输出 (C++,Python)

本节对应赵虚左ROS书籍的1.4.2 1)创建工作空间 mkdir -p catkin_ws/src cd catkin_ws catkin_make 2) 终端进入VS Code code . 3) vscoe 的基本配置 3.1)修改.vscode/tasks.json ,修改内容如下: { // 有关 tasks.json 格式的文档,请参见…

SAP系统中MD01与MD02区别

知识点普及-MD01与MD02区别 1、从日常业务中,我们都容易知道MD01是运行全部物料,MD02是运行单个物料 2、在做配置测试中,也出现过MD02可以跑出物料,但是MD01跑不出的情况。 3、MD01与MD02的差异: 3.1、只要在物料主数…

快速迭代收缩-阈值算法(FISTA)

文章目录 1. 数学与优化基础2. FISTA 算法的原理、推导与机制3. Matlab 实现4. FISTA 在图像处理与压缩感知中的应用4.1. 基于小波稀疏先验的图像去噪4.2 压缩感知图像重建 1. 数学与优化基础 在许多信号处理与机器学习问题中,我们希望获得稀疏解,即解向…

微服务之间打通用户上下文

微服务之间打通用户上下文 打通上下文步骤需求:1、gateway网关登录拦截器:【LoginFilter】解释:代码 2、SpringMVC全局处理:【GlobalConfig】解释:代码: 3、自定义登录拦截器:【LoginIntercepto…

Hutool之DateUtil:让Java日期处理变得更加简单

前言 在Java开发中,日期和时间的处理是一个常见问题。为了简化这个过程,许多开发者会使用第三方工具包,如Hutool。Hutool是一个Java工具包,提供了许多实用的功能,其中之一就是日期处理。日期时间工具类是Hutool的核心包…

ES中常用的Query和查询作用,以及SpringBoot使用实例

ES中常用的Query和查询作用,以及 SpringBoot 使用实例 文章目录 ES中常用的Query和查询作用,以及 SpringBoot 使用实例MatchAllQueryTermQueryBoolQueryRangeQueryMatchQueryMultiMatchQueryTermsQueryPrefixQueryWildcardQueryRegexpQueryFuzzyQueryDis…

Flutter 自定义插件基础

1、Flutter插件是什么?官方插件库 在开发Flutter应用过程中会涉及到平台相关接口调用,例如数据库操作、相机调用、外部浏览器跳转等业务场景。其实Flutter自身并不支持直接在平台上实现这些功能,而是通过插件包接口去调用指定平台API从而实现…

极狐GitLab 外部授权控制机制是怎样的?

极狐GitLab 是 GitLab 在中国的发行版,关于中文参考文档和资料有: 极狐GitLab 中文文档极狐GitLab 中文论坛极狐GitLab 官网 外部授权控制 (BASIC SELF) 在高度控制的环境中,访问策略可能需要由外部服务控制,该服务允许基于项目…

Linux系统之----冯诺依曼结构

1.简要描述 冯诺依曼体系结构是现代计算机的基本设计思想,其核心理念是将计算机的硬件和软件统一为一个整体,通过存储程序的方式实现计算。冯诺依曼体系结构的核心思想是通过存储程序实现自动计算,其五大部件协同工作,奠定了现代…

【八股】计算机网络

1 概述 1.1 网络的网络 网络把主机连接起来,而互连网(internet)是把多种不同的网络连接起来,因此互连网是网络的网络。而互联网(Internet)是全球范围的互连网。 1.2 ISP 互联网服务提供商 ISP 可以从互联网管理机构获得许多 IP 地址,同时拥有通信线路以及路由器等联…

基于VS Code 为核心平台的python语言智能体开发平台搭建

以下是基于 VS Code 为核心平台,整合 Node-RED、Gradio、Docker Desktop 的智能体可视化开发平台优化方案,聚焦工具链深度集成与开发效率提升: 一、核心架构设计 #mermaid-svg-f8l9kYPAlJ2TlpGF {font-family:"trebuchet ms",verd…

STM32G0单片机自带RTC

STM32有个自带RTC外设,外接32.768KHz的晶振后可得到相对精确的计时功能。 实测了一个一小时快个1秒多。 1 cubeMX设置了RTC后自动生成的初始化代码如下 static void MX_RTC_Init(void) {/* USER CODE BEGIN RTC_Init 0 *//* USER CODE END RTC_Init 0 */RTC_TimeT…

细说STM32单片机FreeRTOS任务管理API函数及多任务编程的实现方法

目录 一、FreeRTOS任务管理API函数 1、任务管理API函数 2、获取任务的句柄 (1)函数xTaskGetCurrentTaskHandle() (2)函数xTaskGetIdleTaskHandle() (3)函数xTaskGetHandle() 3、单个任务的操作 &a…

星露谷物语 7000+ 大型MOD整合包

衣服美化、家具美化、地图美化、人物肖像美化 全地图装修存档、人物美化、扩展包、环境美化、家具、动植物、通用前置包、新增NPC、功能、服装发饰妆 帽子发型农场小镇美化大型玩法拓展实用功能mod 动漫人物形象MOD 地点/动物/地图/功能/机械/家具/建筑/界面美化/扩展/农场/食谱…

C++ `unique_ptr` 多线程使用

C unique_ptr 多线程使用 一、核心结论 操作同一个 unique_ptr:必须加锁(所有权转移是非原子操作)访问被管理对象:若对象非线程安全,仍需额外同步独立 unique_ptr 实例:不同线程操作不同实例时无需加锁 二…

Android audio系统六 AudioEffect音效加载

对于Android系统智能硬件设备,音效处理的实现方式有以下几种: AudioEffect – android系统音效处理 优点:纯软件实现,移植调试简单方便 缺点:cpu上运行,容易因为资源竞争而出现卡顿 DSP/ADSP – 数字信号处…

深度学习总结(21)

超越基于常识的基准 除了不同的评估方法,你还应该了解的是利用基于常识的基准。训练深度学习模型,你听不到也看不到。你无法观察流形学习过程,它发生在数千维空间中,即使投影到三维空间中,你也无法解释它。唯一的反馈…

接口自动化测试(二)

一、接口测试流程:接口文档、用例编写 拿到接口文档——编写接口用例以及评审——进行接口测试——工具/自动化框架进行自动化用例覆盖(70%)——输出测试报告 自动化的目的一般是为了回归 第一件事情:理解需求,学会看接口文档 只需要找到我…