一文彻底弄懂Spring Boot的启动过程
一,Spring Boot启动过程
1. 启动入口
Spring Boot 应用的启动入口通常是一个包含 @SpringBootApplication 注解的主类,并调用 SpringApplication.run() 方法。@SpringBootApplication 是一个复合注解,包含了 @Configuration、@EnableAutoConfiguration 和 @ComponentScan,从而开启了自动配置和组件扫描。
源码路径在 SpringApplication 类的 run() 方法:
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return new SpringApplication(primarySource).run(args);
}
这个入口主要做了以下几件事情:
- 创建 SpringApplication 实例:初始化 Spring Boot 环境。
- 初始化环境和监听器:设置启动的
Environment,并且添加ApplicationListener监听器。 - 准备和刷新 Spring 上下文:通过
prepareContext和refreshContext方法进行上下文环境的准备和刷新。
2. 创建 SpringApplication 实例
在 SpringApplication 的构造方法中,Spring Boot 解析应用的启动模式(例如是 Web 应用、Servlet 应用或是普通应用),并初始化应用的上下文类型。Spring Boot 的不同上下文类型包括 AnnotationConfigApplicationContext(非 Web 应用)和 AnnotationConfigServletWebServerApplicationContext(Web 应用)。
3. 初始化 Environment 和监听器
接下来,Spring Boot 会初始化 ConfigurableEnvironment,这个环境中包含了系统的属性、环境变量、配置文件等数据,作为后续加载 Bean 定义和初始化的基础。
同时,Spring Boot 也会初始化一系列的 ApplicationListener,用于监听和处理应用启动过程中的事件,比如 ApplicationEnvironmentPreparedEvent、ApplicationPreparedEvent 等。
4. 加载配置类并触发自动配置
Spring Boot 使用 @EnableAutoConfiguration 注解触发自动配置,核心实现是在 AutoConfigurationImportSelector 中加载 META-INF/spring.factories 配置文件,文件中列出了许多自动配置类(如 DataSourceAutoConfiguration、JpaRepositoriesAutoConfiguration 等),根据条件(例如某些 Bean 是否存在、某些属性是否被配置等)加载相应的自动配置。
5. 加载并注册 Bean
在 refreshContext() 方法中,Spring Boot 调用 refresh() 方法,这一步骤中完成了 BeanFactory 的初始化和 BeanPostProcessor 的注册,并解析 @Component、@Service、@Repository 等注解标注的 Bean 定义,将它们注册到 BeanFactory 中。
在源码层面,refresh() 方法中,invokeBeanFactoryPostProcessors 和 registerBeanPostProcessors 这两个方法是关键,分别用于执行所有 BeanFactoryPostProcessor 和 BeanPostProcessor,确保 Bean 的生命周期正确管理。
6. Web 环境中的嵌入式容器启动
在 Web 应用中,Spring Boot 会启动嵌入式 Web 容器(如 Tomcat 或 Jetty)。Spring Boot 默认通过 ServletWebServerApplicationContext 启动内嵌的 Web 服务器。在 refresh() 的最后,会启动嵌入式容器,将应用作为 Web 应用发布。
7. 执行 ApplicationRunner 和 CommandLineRunner
Spring Boot 启动完成后,会扫描并执行所有实现了 ApplicationRunner 和 CommandLineRunner 接口的 Bean。它们可以用于在启动后执行自定义逻辑。
8. 发布应用启动完成事件
最后,Spring Boot 发布 ApplicationReadyEvent 事件,通知所有监听器应用已启动完成。至此,Spring Boot 应用正式启动完成,可以接收 HTTP 请求或执行其他任务。
二、Spring Boot 启动过程的架构设计
在 Spring Boot 应用启动的过程中,SpringApplication.run() 是最常用的启动方式。通过这个方法,Spring Boot 为开发者屏蔽了大量复杂的初始化细节,我们只需提供主启动类的入口和简单的配置信息即可启动整个应用。
下面我们从源码入手,分步骤分析 SpringApplication.run 进行的操作。
1,SpringApplication.run() 的详细流程
SpringApplication.run 主要完成以下几大步骤:
初始化 SpringApplication 实例:
该实例负责整个 Spring Boot 应用的启动过程,通过判断应用类型和设置环境变量为后续配置加载和应用上下文创建提供基础。核心方法为
SpringApplication#prepareEnvironment和SpringApplication#createApplicationContext。创建应用上下文并刷新上下文:
SpringApplication将根据应用类型来创建不同的ApplicationContext(如AnnotationConfigApplicationContext或ServletWebServerApplicationContext),并将所有Bean装载到上下文中。加载环境配置:
Spring Boot 会基于开发环境或生产环境加载不同的配置文件。核心是
ConfigFileApplicationListener监听配置事件,解析应用配置文件(application.properties或application.yml)并装配到应用上下文的Environment对象中。启动嵌入式容器:
如果是 Web 应用,Spring Boot 会启动内嵌的服务器(如 Tomcat、Jetty 或 Undertow),并将
DispatcherServlet注册到服务器中。
2,SpringApplication 核心设计类
在源码层面,SpringApplication 是启动过程中的关键类。它通过构造函数和 run() 方法完成了以下工作:
public class SpringApplication {
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = deduceWebApplicationType();
this.setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
this.setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
public ConfigurableApplicationContext run(String... args) {
// 初始化阶段
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
context = createApplicationContext();
prepareContext(context, environment, listeners, applicationArguments);
refreshContext(context);
afterRefresh(context, applicationArguments);
listeners.started(context);
callRunners(context, applicationArguments);
} catch (Exception ex) {
handleRunFailure(context, listeners, ex);
throw new IllegalStateException(ex);
} finally {
stopWatch.stop();
}
return context;
}
}
源码揭示了 SpringApplication 的核心功能在于配置监听器、加载环境、创建上下文等几个方面。通过 deduceWebApplicationType() 方法判断 Web 应用类型,为接下来的 ApplicationContext 选择提供了依据。这种设计实现了启动流程的高度定制和灵活适应性。
三、启动过程中的关键组件及设计模式
Spring Boot 的启动流程涉及到多个设计模式,这些模式不仅提升了代码的可读性和灵活性,也确保了在不同的业务场景下能够快速地调整启动行为。
1,ApplicationContext 和 ConfigurableEnvironment
ApplicationContext 是 Spring 框架的核心概念之一,提供了 IoC 容器的实现和应用上下文的管理。在 Spring Boot 中,ApplicationContext 包含了所有的 Bean,并提供了应用与外部环境的接口,如 ConfigurableEnvironment,用于管理应用的环境配置和属性。
ApplicationContext 的核心设计
ApplicationContext 是一个高度抽象的接口,它有多个具体实现,如 AnnotationConfigApplicationContext 和 ServletWebServerApplicationContext。Spring Boot 的 SpringApplication 会根据应用的类型选择合适的 ApplicationContext 实现,并对其进行配置和初始化:
- 非 Web 应用:使用
AnnotationConfigApplicationContext加载配置类和 Bean。 - Web 应用:使用
ServletWebServerApplicationContext,并加载嵌入式 Web 容器配置。
这种设计模式提升了 ApplicationContext 的适配性,使其可以灵活适应各种不同类型的应用。
ConfigurableEnvironment 环境配置的加载
ConfigurableEnvironment 提供了应用的环境信息,包括系统属性、环境变量以及外部化的配置文件内容。在 Spring Boot 启动过程中,ConfigurableEnvironment 会在 prepareEnvironment() 方法中被初始化。
在 ConfigurableEnvironment 中,属性源的优先级管理极大地提升了配置的灵活性。Spring Boot 根据不同的 PropertySource(如 application.properties、环境变量、命令行参数等),提供优先级管理,这样使得在复杂环境下也能灵活覆盖配置。
2,ApplicationListener 事件驱动模型
ApplicationListener 是 Spring 框架中事件驱动机制的实现,Spring Boot 使用该机制管理启动过程中各类重要事件。以下是常见的启动事件:
ApplicationStartingEvent:在SpringApplication.run()开始时触发。ApplicationEnvironmentPreparedEvent:在Environment准备好之后触发。ApplicationPreparedEvent:在上下文加载完成之前触发。ApplicationStartedEvent:在上下文刷新完成之后触发。ApplicationReadyEvent:应用启动完成时触发。
事件驱动模型允许开发者在应用启动的各个阶段进行自定义操作,极大增强了扩展性和灵活性。
3,依赖注入和条件配置加载机制
Spring Boot 中自动配置依赖于条件加载机制,核心在于 @Conditional 系列注解,比如 @ConditionalOnClass 和 @ConditionalOnMissingBean 等。这些注解允许 Spring Boot 根据实际情况有选择性地加载配置:
@Configuration
@ConditionalOnClass(DataSource.class)
public class DataSourceAutoConfiguration {
// 数据源自动配置逻辑
}
这种条件注入模式通过按需加载 Bean,有效减少了资源的浪费。此设计模式也让 Spring Boot 能够快速适应不同的环境和依赖场景,避免不必要的 Bean 被加载。
四、自动配置的实现机制
Spring Boot 通过 @EnableAutoConfiguration 启动自动配置功能,它的底层实现机制是 AutoConfigurationImportSelector 和 SpringFactoriesLoader,其中 SpringFactoriesLoader 会扫描 META-INF/spring.factories 文件加载自动配置类。
1,SpringFactoriesLoader 详解
Spring Boot 自动配置的一个核心机制是 SpringFactoriesLoader,它会从 META-INF/spring.factories 文件中加载所有需要自动配置的类。这种文件配置的方式让 Spring Boot 能够轻松拓展新的自动配置功能。
@EnableAutoConfiguration 通过 AutoConfigurationImportSelector 来加载自动配置类,自动配置的实现类和逻辑则由 @Conditional 注解管理,这样使得自动配置具有按需加载的特性。
2,条件注解与场景应用
自动配置机制在实际业务中应用非常广泛。举例来说,Spring Boot 中数据源的配置是通过 DataSourceAutoConfiguration 类实现的。在 DataSourceAutoConfiguration 中,使用 @ConditionalOnClass 注解来判断类路径中是否存在 DataSource 类,若存在,则注入数据源配置。
这类场景非常适合用在多数据源配置上,开发者可以利用条件注解在生产环境下配置多个数据库源,而在测试环境中只加载测试数据源配置。
五、嵌入式容器的启动流程
Spring Boot 在 Web 应用中默认使用嵌入式容器,这样可以使应用独立于外部服务器而运行,提升了应用的独立性和便捷性。
1,嵌入式容器的启动设计
在 Spring Boot 启动 Web 应用时,会根据 ServletWebServerApplicationContext 加载嵌入式容器。以下是启动嵌入式容器的关键流程:
- 创建 ServletWebServerApplicationContext:Spring Boot 会选择 Web 应用的上下文类
ServletWebServerApplicationContext,并加载WebServerFactory工厂类。 - 创建 Web 服务器:Spring Boot 使用工厂类创建内嵌的 Web 服务器,如 Tomcat、Jetty 或 Undertow。
- 注册 DispatcherServlet:Spring Boot 将
DispatcherServlet注册到内嵌服务器中,并通过ServletRegistrationBean对其进行初始化和配置。
这种设计模式使得 Spring Boot 应用无缝支持不同类型的 Web 容器,并根据环境灵活选择合适的容器。
2,嵌入式容器在多环境中的应用
嵌入式容器尤其适合微服务架构,它能在容器化场景中快速适应 Docker 或 Kubernetes 等部署平台。此外,Spring Boot 允许开发者通过简单配置切换不同类型的 Web 服务器(如 Tomcat 到 Jetty),这样的设计为企业级应用提供了高灵活性。
一文彻底弄懂Spring Boot的启动过程的更多相关文章
- spring boot容器启动详解
目录 一.前言 二.容器启动 三.总结 =======正文分割线====== 一.前言 spring cloud大行其道的当下,如果不了解基本原理那么是很纠结的(看见的都是约定大于配置,但是原理呢?为 ...
- spring boot无法启动,或者正常启动之后无法访问报404的解决办法
以前用spring boot都是用idea的自动创建,或者是用的Jhipster创建的,就没有深究怎么去搭建.但是今天晚上心血来潮,想自己搭一个demo来整合一些技术,于是就花一点时间来手动搭.因为今 ...
- 让Spring Boot项目启动时可以根据自定义配置决定初始化哪些Bean
让Spring Boot项目启动时可以根据自定义配置决定初始化哪些Bean 问题描述 实现思路 思路一 [不符合要求] 思路二[满足要求] 思路三[未试验] 问题描述 目前我工作环境下,后端主要的框架 ...
- Spring boot自定义启动字符画(banner)
spring boot项目启动时会打印spring boot的ANSI字符画,可以进行自定义. 如何自定义 实现方式非常简单,我们只需要在Spring Boot工程的/src/main/resourc ...
- spring boot 项目启动无任何反应
遇到的问题 spring boot项目启动后无任何报错,ps有进程,nohub无日志 定位 更换jar包,问题依然存在,将jar包放到其他服务器,运行正常,排除打包问题 同服务器其他系统运行正常,但停 ...
- Spring Boot定制启动图案
启动图案 Spring Boot在启动的时候会显示一个默认的Spring的图案,对应的类为SpringBootBanner. . ____ _ __ _ _ /\\ / ___'_ __ _ _(_) ...
- Spring Boot 设置启动时banner
Spring Boot项目再启动的时候默认会在控制台输出一个字符banner图案,如下图: 我们可以通过下面的方法关闭启动时显示字符banner图案: 关闭banner方法一: public stat ...
- spring boot应用启动原理分析
spring boot quick start 在spring boot里,很吸引人的一个特性是可以直接把应用打包成为一个jar/war,然后这个jar/war是可以直接启动的,不需要另外配置一个We ...
- Spring Boot应用启动原理分析(转)
在spring boot里,很吸引人的一个特性是可以直接把应用打包成为一个jar/war,然后这个jar/war是可以直接启动的,不需要另外配置一个Web Server. 如果之前没有使用过sprin ...
- Spring Boot SpringApplication启动类(二)
目录 前言 1.起源 2.SpringApplication 运行阶段 2.1 SpringApplicationRunListeners 结构 2.1.1 SpringApplicationRunL ...
随机推荐
- lbs 地理位置
lbs 地理位置 https://caorong.github.io/2018/05/04/lbs/ https://www.cnblogs.com/lbser/p/3310455.html http ...
- shiro的rememberMe各种漏洞一刀切解决
rememberMe的低版本AES固定密码导致的漏洞,高版本仍然有被爆破,穷举的风险等.这种东西总是在安全检测的时候被拿出来说事儿,然而项目中并未开启rememberMe,也就是说压根不需要这个功能. ...
- docker network macvlan
ref: Docker 网络模型之 macvlan 详解,图解,实验完整 网卡也能虚拟化?网卡虚拟化技术 macvlan 详解 docker和macvlan与host互通
- 像 Mysql 和 MongoDB 这种大型软件在设计上都是精益求精的,它们为什么选择B树,B+树这些数据结构?
为什么 MongoDB (索引)使用B-树而 Mysql 使用 B+树? B 树与 B+ 树,其比较大的特点是:B 树对于特定记录的查询,其时间复杂度更低.而 B+ 树对于范围查询则更加方便,另外 B ...
- 如何在 Nuxt 3 中有效使用 TypeScript
title: 如何在 Nuxt 3 中有效使用 TypeScript date: 2024/9/9 updated: 2024/9/9 author: cmdragon excerpt: 摘要:本文详 ...
- 中文关键字检索分析-导出到csv或者excel-多文件或文件夹-使用python和asyncio和pandas的dataframe
1.02版本 把原来的tab一个个拼接成文件输出,改成pandas的dataframe 使用asyncio库来使用协程,但是测试下来速度好像是差不多的.可能速度太快了,没能很好的测出来差异. 原来的最 ...
- Angular 学习笔记 (Typescript 版本更新)
记入每一期我会用到的功能 : v4.3 1. getter setter 的类型可以不一样咯, 这个困扰很久了. (interface, object, class 都可以这样用) class T ...
- 图解MQTT概念、mosquitto编译和部署 ,写代码,分别使用外网和本地服务器进行测试
前沿提要: MQTT是什么不知道? 看这一篇:https://www.cnblogs.com/happybirthdaytoyou/p/10362336.html 阿里云官网玩不转? 看这一篇: ht ...
- [OI] 二项式期望 DP
OSU OSU yet Another OSU yet yet Another OSU OSU 的题目是这样的:有一些相邻的块,给定每一个块的联通概率,每个连通块对答案有 \(size^{3}\) 的 ...
- 对于python中GIL的一些理解与代码实现
近期看了一些关于GIL的一些内容,敲一下代码看看效果. # coding:utf-8 # GIL(Global Interpreter Lock):他只允许任何时刻只有一个线程处于执行状态,即使是在具 ...