SpringApplication的基础特性
在 Spring Boot 生态中,SpringApplication
是启动应用的核心类,它封装了繁杂的启动流程,让开发者能以简洁方式启动应用,同时还提供了丰富可扩展点,助力定制化需求实现。
在这里我会先讲基础特性,然后会再将一些 SpringApplication 的基础原理
自定义 banner
什么是 banner
banner 是 Spring Boot 应用启动时,在控制台打印的一段字符图案或文字信息,默认是 Spring Boot 标志性图案。它虽不影响应用功能,却能成为应用独特 “名片”,用于展示团队文化、应用名称等个性化内容。

这个就是 banner
这个东西是可以自定义的
如何自定义 banner
在类路径(一般是 src/main/resources
目录)下添加
banner.txt
文件,Spring Boot 启动时会自动加载该文件内容作为
banner 显示。可借助在线工具(如文中提到的 Spring Boot banner
在线生成工具,访问 banner-bootschool.net
),生成个性文字图案(支持英文等),下载后替换 banner.txt
文字,轻松定制专属启动画面。
若不想用默认 banner.txt
文件名或存放路径,可通过配置
spring.banner.location
实现。例如在
application.yml
中配置:
1 | spring: |
这样 Spring Boot 启动时,就会加载 classpath:custom/
路径下的 banner-custom.txt
文件作为 banner 。
除了文本格式的 banner.txt
,Spring Boot
还支持其他格式:
- banner.gif/banner.jpg/banner.png :若在类路径放置这些图片格式文件,Spring Boot 会将图片转换为字符画形式展示(效果取决于图片复杂度和终端支持度)。
- 配置控制显示:通过
spring.banner.image.*
系列配置,如spring.banner.image.width
、spring.banner.image.height
等,可调整图片转字符画的显示参数,精细控制 banner 呈现效果 。
1 | spring.main.banner-mode=off |
自定义 SpringApplication:灵活把控启动配置
直接创建与配置
SpringApplication
对象
在 Spring Boot 应用中,常规启动方式是借助
@SpringBootApplication
注解和
SpringApplication.run(MyApplication.class, args)
简洁启动
1 |
|
我们来看看SpringApplication
中涉及到run
的源代码
1 | public class SpringApplication { |
这两个静态方法本质上是快速创建 SpringApplication
对象并执行启动流程的
“语法糖”。但在实际开发中,若想对应用启动进行更细粒度的配置,就需要手动创建
SpringApplication
对象,深入参与启动过程。
常规的静态 run
方法虽然简洁,但封装了很多默认的启动逻辑。当我们需要自定义一些启动行为时,手动创建
SpringApplication
对象就显得尤为必要。示例代码如下:
1 | import org.springframework.boot.Banner; |
在这个过程中:
- 创建对象:
new SpringApplication(MyApplication.class)
明确指定了应用的主配置类,SpringApplication
会基于此去扫描和加载相关的 Bean 定义、自动配置等内容。 - 配置行为:通过
setBannerMode
方法,能灵活控制 banner 显示状态。除了OFF
(关闭),还有CONSOLE
(控制台显示,默认)、LOG
(记录到日志文件)模式。比如在生产环境,为避免控制台冗余输出,可设置为OFF
;开发环境下,为了直观看到 Spring Boot 标志性启动画面或者自定义的 banner ,则可使用CONSOLE
模式 。 - 执行启动:调用
run(args)
方法,才真正触发 Spring Boot 应用的启动流程,包括环境准备、上下文创建、Bean 加载和初始化等一系列操作。
注意,在程序化调整 SpringApplication 的启动参数的时候,它的优先级是低于配置文件的优先级的,这个可以看配置注入的源码,你会发现是为什么是这个顺序。
Builder
方式构建 SpringApplication
——FluentBuilder API
在 Spring Boot 中,除了直接通过 SpringApplication
的构造方法、静态 run
方法来创建和启动应用,还提供了
FluentBuilder API(流畅构建器 API) ,也就是基于
SpringApplicationBuilder
的构建方式。它通过链式调用的语法,让开发者更灵活、更直观地配置
SpringApplication
的各项参数
定制化 Banner + 激活配置文件示例
1 | new SpringApplicationBuilder(MyApp.class) |
SpringApplicationBuilder
支持链式调用,一步一步配置应用的各种属性,最后调用 run(...)
启动应用。
1 | new SpringApplicationBuilder(MyApp.class) |
如果不用 Builder,等价逻辑需要这样写:
1 | SpringApplication app = new SpringApplication(MyApp.class); |
可以看到,Builder 方式通过链式调用,把零散的 set
操作串起来
SpringApplicationBuilder
内部其实是对
SpringApplication
和 ApplicationContext
构建过程的封装,核心思路是:
封装配置参数: 把
SpringApplication
的各种配置项(如webApplicationType
、bannerMode
、initializers
等),以及ApplicationContext
的相关配置,通过 Builder 的方法暴露出来,让开发者用链式调用设置。延迟构建
SpringApplication
: 调用run(...)
之前,Builder 只是在收集配置;真正调用run(...)
时,才会根据收集的配置,创建SpringApplication
实例,并执行启动流程。支持父子上下文: 这是 Builder 方式的一个重要特性!它可以方便地构建 父子 Spring 上下文(比如父上下文放公共配置,子上下文放 Web 相关配置),典型代码:
1
2
3
4
5
6
7
8
9
10public static void main(String[] args) {
new SpringApplicationBuilder()
// 父上下文:加载公共配置
.parent(CommonConfig.class)
.web(WebApplicationType.NONE) // 父上下文是非 Web 类型
// 子上下文:加载 Web 相关配置
.child(WebConfig.class)
.web(WebApplicationType.SERVLET) // 子上下文是 Servlet Web 类型
.run(args);
}父上下文
CommonConfig
里的 Bean(如DataSource
),子上下文WebConfig
可以直接引用。子上下文的 Bean(如
UserController
)不会污染父上下文,实现了一定程度的 “模块隔离”。
SpringApplicationBuilder
提供了丰富的链式方法,常用的有这些:
方法名 | 作用 |
---|---|
sources(...) |
指定主配置类(等价于 SpringApplication 构造方法里的
primarySources ) |
web(WebApplicationType) |
设置 Web 应用类型(SERVLET/REACTIVE/NONE) |
banner(Banner) |
自定义启动 Banner(比如替换成自己的 ASCII 艺术字) |
bannerMode(Banner.Mode) |
控制 Banner 显示方式(CONSOLE/LOG/OFF) |
logStartupInfo(boolean) |
是否打印启动日志(默认 true ) |
initializers(...) |
添加 ApplicationContextInitializer 初始化器 |
listeners(...) |
添加 ApplicationListener 监听器 |
parent(...) / child(...) |
构建父子上下文(父上下文通常放公共 Bean,子上下文放业务 Bean) |
profiles(...) |
指定激活的配置文件(等价于
spring.profiles.active ) |
environment(...) |
自定义 Environment (比如添加额外的属性源) |
SpringApplication的基础原理
@SpringBootApplication
可以发现,Spring Boot
项目中的启动类,总会加一个@SpringBootApplication
,这个就是Spring
Boot启动类的核心部分,它的存在使得项目启动类非常简单,仅仅存在一个注解`@SpringBootApplication
以及一个运行参数为被该注解标注类run函数,基本全部
Spring Boot 的启动准备工作就会被自动完成。
对于该启动类的分析,就从这个Spring
Boot的核心注解@SpringBootApplication
开始入手。
字面分析,这个注解是标注一个Spring Boot应用。
我们进去这个注解的详细信息,去看看它的源码
1 | package org.springframework.boot.autoconfigure; |
进入到这个注解后,可以发现该注解由四个元注解,以及其他三个注解组成分别是
1 | // 表明这是一个 Spring Boot 配置类 |
我们首先从@SpringBootConfiguration
注解看起
@SpringBootConfiguration
1 | package org.springframework.boot; |
可以发现,这个注解中除了元注解之外,存在@Configuration
这个Spring
Framework 提供的原生注解,@SpringBootConfiguration
对其进行了扩展,增加了对 Spring Boot 特性的支持。
那么可以理解@SpringBootConfiguration
为他仅仅就是一个配置类。我们知道,@Configuration
注解为Spring中的配置类注解,其中被@Component
标注为Spring组件,意味着他被注册到IOC容器。
因此,套用官方文档的答案:@SpringBootConfiguration
表示一个类提供
Spring Boot 应用程序@Configuration
。可以用作 Spring
标准@Configuration
注释的替代方法,以便可以自动找到配置(例如在测试中)。
而且,我们可以明确看出 @SpringBootApplication
是整合了
@SpringBootConfiguration
、@EnableAutoConfiguration
和 @ComponentScan
的组合注解,而其中SpringBootApplication
中的属性通过
@AliasFor
映射到底层注解,这种设计让开发者可以在不破坏注解组合的前提下,灵活定制配置
1 |
|
exclude
→ 映射到@EnableAutoConfiguration
的exclude
scanBasePackages
→ 映射到@ComponentScan
的basePackages
proxyBeanMethods
→ 映射到@Configuration
的proxyBeanMethods
@EnableAutoConfiguration
接下来就是咱们比较熟悉的注解,@EnableAutoConfiguration
,它是自动配置原理的核心注解,它的作用就是开启自动配置,进入源码
1 | package org.springframework.boot.autoconfigure; |
可以发现,除了四个元注解,这个注解被两个注解所标注@AutoConfigurationPackage
、@Import(AutoConfigurationImportSelector.class)
我们之前的文章中分析过,@AutoConfigurationPackage
中使用注解@Import
,导入了AutoConfigurationPackages.Registrar.class
到你的Spring容器中,来处理包注册,@SpringBootApplication
这个组合注解包含了
@AutoConfigurationPackage
,所以当你使用
@SpringBootApplication
时,会自动启用包注册功能。在 Spring
Boot 应用启动时,这个注解会和 @EnableAutoConfiguration
一起工作,共同构建自动配置的基础环境。
@EnableAutoConfiguration
还通过@Import(AutoConfigurationImportSelector.class)
引入了关键的导入选择器,其核心逻辑在于AutoConfigurationImportSelector
的selectImports()
方法。该方法会调用SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, classLoader)
,从类路径的META-INF/spring.factories
中获取自动配置类列表。当项目引入某个依赖(如spring-boot-starter-web
)时,其
JAR
包内的spring.factories
会包含对应的自动配置类,从而被SpringFactoriesLoader
扫描到。
所以说,Spring Boot
启动时,通过SpringFactoriesLoader
工具类从类路径下的META-INF/spring.factories
文件中,读取与EnableAutoConfiguration.class
关联的配置类列表,并将这些类导入
Spring 容器,实现自动配置功能。
与`@ComponentScan
相比,二者的区别在于,@AutoConfigurationPackage
主要是为自动配置提供包路径信息,而 @ComponentScan
侧重于组件扫描。
1 | package org.springframework.boot.autoconfigure; |
借助
AutoConfigurationPackages.Registrar
类,把主应用类所在的包路径注册到 Spring 容器中。这些包路径会被存储在BeanDefinitionRegistry
里。其他自动配置类可以通过
AutoConfigurationPackages.get(BeanFactory)
方法获取这些包路径。像 JPA 实体扫描、组件扫描等功能都会依赖这些包路径可以通过
basePackages
参数直接指定包名。这就是为什么 Spring Boot 可以配置自定义扫描路径,也能通过basePackageClasses
参数指定参考类,以类所在的包作为扫描范围。这样就实现了支持灵活的包路径自定义,以满足不同的项目结构需求。@AutoConfigurationPackage
就是添加该注解的类所在的包作为自动配置包进行管理。他的实现就是依赖于工具类AutoConfigurationPackages
中的内部类Registrar
对所标注的包进行注册。
进入到AutoConfigurationPackages
这个类中,就会发现,其中有一些重要的方法来负责管理和存储自动配置的基础包路径,用于扫描组件(如
@Entity
、@Repository
等)以及定位需要自动配置的类时候使用
1 | // |
其中的静态工具方法
1 | public static boolean has(BeanFactory beanFactory) |
- has():检查 BeanFactory 中是否存在自动配置包的注册信息。
- get():获取已注册的基础包路径列表。
- register():向 BeanDefinitionRegistry 注册基础包路径。
进入到内部类结构,可以得到如下
- Registrar:实现
ImportBeanDefinitionRegistrar
,负责注册BasePackages
Bean。 - PackageImports:解析
@AutoConfigurationPackage
注解的属性,提取基础包路径。 - BasePackages:封装基础包路径列表,支持日志记录和路径获取。
简单讲解一下其工作流程
首先当
@AutoConfigurationPackage
注解被处理时,Registrar 方法会被调用,来注册包路径,从注解元数据中提取包路径(通过PackageImports
)。调用AutoConfigurationPackages.register()
注册路径。1
2
3
4
5
6
7
8
9static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, (String[])(new PackageImports(metadata)).getPackageNames().toArray(new String[0]));
}
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}然后
PackageImports
解析注解属性,从中可以发现优先级,basePackages
>basePackageClasses
> 注解所在类的包,按照这个顺序获得packageNames
属性也就是启动类所在的包。,如果用户没有显式指定包,Spring Boot 会使用主应用类所在的包,这就是默认行为的来源1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16private static final class PackageImports {
PackageImports(AnnotationMetadata metadata) {
// 1. 获取 @AutoConfigurationPackage 注解的 basePackages 属性
List<String> packageNames = new ArrayList(Arrays.asList(attributes.getStringArray("basePackages")));
// 2. 获取 basePackageClasses 属性并提取其所在包
for(Class<?> basePackageClass : attributes.getClassArray("basePackageClasses")) {
packageNames.add(basePackageClass.getPackage().getName());
}
// 3. 如果都没指定,则使用注解所在类的包
if (packageNames.isEmpty()) {
packageNames.add(ClassUtils.getPackageName(metadata.getClassName()));
}
}
}BasePackages
管理包路径,这个没啥好讲,就是存储一下基础包路径列表,然后给点日志,这个方法中有这样一条List<String> packages = new ArrayList();
,这个列表将所有自动配置的基础包路径集中存储在一个 Bean 中,便于其他自动配置类统一获取。最后注册
BeanDefinition
,保证基础设施 Bean不对外暴露,通过构造函数参数形式存储包路径数组
Spring Boot 应用程序的启动
继续剖析 run 方法
讲完了注解相关的原理和源代码梳理后,让我们聚焦于应用程序的启动过程,我们来看看Spring Boot项目的启动原理
我们可以看到 SpringBootWebPart1Application
的
main
方法中,直接调用了 SpringApplication
的静态方法 run
,用于启动整个 Spring Boot 项目。Spring Boot
应用的启动通常始于以下代码:
1 |
|
run
方法的源代码我们上面已经讲过了,实际上就是
new
了一个SpringApplication
对象,其构造参数
primarySources
为加载的主要资源类,通常就是
SpringBoot
的入口类,并调用其 run
方法,通过其参数 args
为传递给应用程序的参数信息,然后进行启动后返回一个应用上下文对象
ConfigurableApplicationContext
。
简单来说,这个静态方法实际上执行了两个关键步骤:
- 创建
SpringApplication
实例:封装应用上下文的创建逻辑。 - 调用
run()
方法:启动应用并返回ConfigurableApplicationContext
。
创建SpringApplication
实例的细节原理
在启动的过程中,SpringApplication
会进行实例化,SpringApplication
的初始化过程涉及到其构造函数,它承担了应用启动前的准备工作,如下源码所示
1 | public SpringApplication(Class<?>... primarySources) { |
由上可知,SpringApplication
提供了两个构造方法,而其核心的逻辑都在第二个构造方法中实现。
我们从上述源码可知,SpringApplication
的第二个构造方法有两个参数,分别是:
ResourceLoader resourceLoader
:ResourceLoader
为资源加载的接口,它用于在Spring Boot
启动时打印对应的 banner
信息,默认采用的就是DefaultResourceLoader
,实操过程中,如果未按照
Spring Boot 的 “约定” 将 banner 的内容放置于 classpath
下,或者文件名不是 banner.*
格式,默认资源加载器是无法加载到对应的 banner 信息的,此时则可通过
ResourceLoader
来指定需要加载的文件路径
Class<?>... primarySources
:主要的 bean
来源,该参数为可变参数,默认我们会传入 Spring Boot 的入口类【即
main
方法所在的类】,如上面我们的
DemoApplication
。如果作为项目的引导类,该类需要满足一个条件,就是被注解
@EnableAutoConfiguration
或其组合注解标注。在前面的的部分我们已经知道
@SpringBootApplication
注解中包含了
@EnableAutoConfiguration
注解,因此被
@SpringBootApplication
注解标注的类也可作为参数传入。当然,primarySources
也可传入其他普通类,但只有传入被@EnableAutoConfiguration
标注的类才能够开启 Spring Boot 的自动配置。
而其中 primarySources
这个可变参数的描述有点疑惑,其实primarySources
参数接收的是被@EnableAutoConfiguration
(或@SpringBootApplication
)标注的类,其核心作用是:
- 作为自动配置的触发点:告知 Spring Boot 从该类的注解中获取组件扫描范围、自动配置规则等信息。
- 确定基础包路径:该类所在的包会被
@AutoConfigurationPackage
注册为自动配置的基础扫描路径(参考前文AutoConfigurationPackages
的解析)。
我们来做一个实验,在启动类目录的同级下,再建一个如下SpringBootWebSecondApplication
类
1 | import org.springframework.boot.autoconfigure.SpringBootApplication; |
标记上@SpringBootApplication
注解,然后,改写SpringBootWebPart1Application
原来的启动类
1 | public class SpringBootWebPart1Application { |
项目跑起来是没有任何问题的,并完成自动配置,因此,决定 Spring Boot
启动的入口类并不是一定是 main
方法所在类,而是直接或间接被
@EnableAutoConfiguration
标注的类。即使main
方法所在类未被@SpringBootApplication
标注,只要SpringApplication.run()
传入的primarySources
是被@EnableAutoConfiguration
标注的类,应用仍可正常启动。

这是因为,Spring Boot
的启动逻辑仅依赖primarySources
中的类是否携带@EnableAutoConfiguration
,而main
方法仅作为
Java 程序的入口,二者无直接关联。
primarySources
类所在的包会成为组件扫描的默认基础包,等价于@ComponentScan
未指定basePackages
时的行为。Spring
Boot
启动时候会自动扫描com.example.myapp
及其子包下的@Component
、@Service
等组件。然后primarySources
类上的@EnableAutoConfiguration
会触发AutoConfigurationImportSelector
加载spring.factories
中的自动配置类。若传入的类未标注该注解,自动配置机制将失效。
其实发现源码中,还有一个方法addPrimarySources
,发现可以调用
addPrimarySources
方法来追加额外的
primarySources
,所以,primarySources
支持传入多个类(通过可变参数),多个类的包路径会被合并为组件扫描范围,多个类上的@EnableAutoConfiguration
配置会被合并(如exclude
参数)
1 | public void addPrimarySources(Collection<Class<?>> additionalPrimarySources) { |
构造参数中,这里将 primarySources
参数转换为
LinkedHashSet
集合,并赋值给SpringApplication
的私有成员变量
Set<Class<?>> primarySources
。
1 | this.primarySources = new LinkedHashSet(Arrays.asList(primarySources)); |
Spring Boot项目启动的初始化部分
这次再来看初始化的部分,只挑重要的初始化部分步骤就是如下部分
1 | public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { |
确定 Web 应用类型:
1 | this.webApplicationType = WebApplicationType.deduceFromClasspath(); |
这里调用了 WebApplicationType
的deduceFromClasspath
方法来进行 Web 应用类型的推断
通过检查类路径中的关键类(如javax.servlet.Servlet
或org.springframework.web.reactive.DispatcherHandler
),自动判断应用类型:
- SERVLET:传统 Servlet Web 应用(默认)
- REACTIVE:响应式 Web 应用
- NONE:非 Web 应用
继续翻看其 WebApplicationType
的源码:
1 | // |
我们发现,WebApplicationType
是一个定义了可能的Web应用类型的枚举类,这一判断将直接影响
- 应用上下文类型:决定创建
ServletWebServerApplicationContext
(Servlet)、ReactiveWebServerApplicationContext
(响应式)或普通ApplicationContext
(非 Web)。 - 自动配置规则:不同类型的应用会加载不同的自动配置类(如 WebMvcAutoConfiguration 或 WebFluxAutoConfiguration)。
- 服务启动方式:确定是否启动嵌入式 Web 服务器(如 Tomcat、Netty)。
上来就是一堆常量,这些常量其实就是判断Web应用类型的指示器功能,起到存在则是的作用
1 | private static final String[] SERVLET_INDICATOR_CLASSES = { |
SERVLET_INDICATOR_CLASSES
:Servlet
:所有 Servlet 容器(如 Tomcat)的基础接口,存在即表示可能为 Servlet 应用。ConfigurableWebApplicationContext
:Spring Web 上下文的标准接口,标志着 Web 相关配置的存在。
WEBMVC_INDICATOR_CLASS
:DispatcherServlet
是 Spring MVC 的核心控制器,存在即表示传统 Web MVC 应用。
WEBFLUX_INDICATOR_CLASS
:DispatcherHandler
是 Spring WebFlux 的核心处理器,存在即表示响应式 Web 应用。
deduceFromClasspath ()
实现了推断web应用类型的逻辑,分布推断的流程如下:
- 响应式应用优先原则:
- 检查是否存在
org.springframework.web.reactive.DispatcherHandler
(响应式核心处理器)。 - 同时确保不存在
DispatcherServlet
(Servlet Web MVC 核心)和ServletContainer
(Jersey 容器)。 - 场景:当项目依赖
spring-boot-starter-webflux
时,此条件成立。
- 检查是否存在
- Servlet 应用的必要条件:
- 验证
SERVLET_INDICATOR_CLASSES
中的所有类是否存在:jakarta.servlet.Servlet
:Servlet 规范核心接口。org.springframework.web.context.ConfigurableWebApplicationContext
:Web 上下文接口。
- 场景:引入
spring-boot-starter-web
后,这两个类必然存在
- 验证
- 非 Web 应用的判定:
- 若上述条件均不满足(如仅包含业务依赖,无 Web 相关库),则判定为
NONE
- 若上述条件均不满足(如仅包含业务依赖,无 Web 相关库),则判定为
而deduceFromClasspath()
方法中调用了ClassUtils.isPresent ()
方法实现了类存在性检查,该工具类方法就是通过反射创建指定的类,根据在创建过程中是否抛出异常来判断该类是否存在。
1 | public static boolean isPresent(String className, { ClassLoader classLoader) |
- 通过
Class.forName()
尝试加载类,若抛出ClassNotFoundException
或NoClassDefFoundError
,则认为类不存在。 false
参数表示不初始化类,仅验证类是否存在,避免触发静态代码块或构造函数。
判断完Web 应用类型之后,就决定了
createApplicationContext()
方法创建的上下文类型,实现了类型推断
加载应用初始化器(ApplicationContextInitializer
):
1 | this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class)); |
这个就涉及到上面自动配置哪里讲的了,从META-INF/spring.factories
加载所有实现ApplicationContextInitializer
的类,用于在上下文刷新前进行自定义配置。
加载流程如下
首先,从
spring.factories
读取,getSpringFactoriesInstances()
方法扫描类路径下所有META-INF/spring.factories
,提取ApplicationContextInitializer
的实现类。加载内置的初始化器,Spring的内置初始化器有很多,分别负责不同的作用然后通过反射创建实例,按
@Order
注解或Ordered
接口排序。之后在
prepareContext()
阶段调用:1
2
3
4
5
6
7
8private void prepareContext(ConfigurableApplicationContext context, ...) {
// ...(其他准备上下文的逻辑,比如加载环境、配置日志等)
// 关键调用:应用所有的 ApplicationContextInitializer 初始化器
applyInitializers(context);
// ...(后续继续准备上下文的逻辑,比如加载 bean 定义等)
}在这一步中,
prepareContext
是SpringApplication
中负责「准备应用上下文」的核心方法之一,在 Spring Boot 启动流程里,会在创建好ConfigurableApplicationContext
(如AnnotationConfigServletWebServerApplicationContext
等)后,执行一系列准备动作,applyInitializers(context)
就是其中一步,目的是让所有注册好的ApplicationContextInitializer
有机会在上下文正式刷新(refresh
)前,对其做自定义初始化配置。可以简单理解为:Spring Boot 先把上下文的 “壳” 建好,然后在真正往里面加载 Bean、刷新容器之前,让各种初始化器先 “装饰” 一下这个上下文,给后续流程提前设置好一些参数、环境等。
之后
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16protected void applyInitializers(ConfigurableApplicationContext context) {
// 遍历所有获取到的 ApplicationContextInitializer 初始化器
for (ApplicationContextInitializer initializer : getInitializers()) {
// 解析 initializer 所要求的上下文类型(泛型解析)
// 比如有的 Initializer 可能限定只能处理 ServletWebServerApplicationContext,这里就会解析出对应的类型
Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
initializer.getClass(), ApplicationContextInitializer.class);
// 断言当前上下文 context 是符合 initializer 要求的类型
// 如果类型不匹配,会抛出异常,阻止启动,避免初始化器执行时报错
Assert.isInstanceOf(requiredType, context, "Unable to call initializer");
// 真正执行初始化逻辑,把当前上下文交给 initializer 去“初始化”
initializer.initialize(context);
}
}从
SpringApplication
中获取所有已经注册好的ApplicationContextInitializer
列表。这些初始化器可能来自:spring.factories
中配置的默认实现(Spring Boot 自身或第三方框架注册的);开发者通过
SpringApplication.addInitializers(...)
手动添加的自定义实现。
利用 Spring 的
GenericTypeResolver
工具,解析ApplicationContextInitializer
实现类上的泛型参数。用 Spring 的
Assert
断言工具,检查当前要初始化的context
(实际的应用上下文对象,如ServletWebServerApplicationContext
)是否是requiredType
及其子类 / 实现类。 如果类型不匹配,直接抛出异常,让开发者能快速发现 “某个初始化器期望的上下文类型和实际创建的上下文不兼容” 问题回调初始化器的
initialize
方法,把当前准备好的应用上下文对象传递进去,让初始化器执行自定义逻辑。 比如有的初始化器可能会往上下文里添加自定义的PropertySource
(属性源)、提前注册一些特殊的 Bean 定义,或者设置上下文的一些环境变量等,为后续 Spring 容器正式刷新(refresh
)做准备。
ApplicationContextInitializer
是 Spring
框架中的一个接口,是 Spring
框架提供的一个强大扩展点,它的主要作用是在Spring容器刷新之前初始化
ConfigurableApplicationContext
,允许开发者在 Spring
容器刷新(refresh()
)之前
对应用上下文进行自定义配置。这个刷新时机往细致了说就是,在
SpringApplication.run(...)
流程中,创建完
ConfigurableApplicationContext
后,执行
refresh
之前。也就是说,此时 Bean
还没开始加载、创建,但是上下文的 “基础环境”(如
Environment
、BeanFactory
等)已经准备好,初始化器可以在这个阶段对上下文做一些 “前期定制”。
1 |
|
SpringApplication.run()
方法中,prepareContext()
阶段调用初始化器(早于 Bean 定义加载和自动配置)。- 接收
ConfigurableApplicationContext
,可修改上下文配置(如添加属性源、注册 Bean 等)。
这样整套流程下来,你就能明白:ApplicationContextInitializer
是 Spring Boot
留给开发者(以及框架自身)在容器启动最早期介入、定制上下文的 “钩子”,而
prepareContext
+ applyInitializers
就是触发这些钩子的关键逻辑
,通过泛型解析、类型校验、遍历执行,确保各种初始化器能安全、有序地对应用上下文做初始化配置。
注册应用监听器(ApplicationListener
):
1 | this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class)); |
加载所有实现ApplicationListener
的类,监听应用生命周期事件(如启动、失败、停止等)。
ApplicationListener
是 Spring
框架提供的一个事件监听机制,它是Spring
应用内部的事件驱动机制,通常被用于监控应用内部的运行状况。其实现的原理是
观察者设计模式,该设计模式的初衷是为了实现系统业务逻辑之间的解耦,从而提升系统的可扩展性和可维护性
我不熟监听器,不说了
确定主应用类:
1 | this.mainApplicationClass = deduceMainApplicationClass(); |
通过分析调用栈,找出包含main
方法的类,用于后续日志和上下文信息。
这是Spring Boot启动的最后一步,调用 SpringApplication
的
deduceMainApplicationClass
方法来进行入口类的推断:
1 | private Class<?> deduceMainApplicationClass() { |
Spring Boot 启动后,会在日志里输出类似这样的内容:
1 | Started DemoApplication in 2.345 seconds (JVM running for 3.456) |
这里的 DemoApplication
就是
主应用类(即 main
方法所在类)。它的作用包括:
- 日志标识:启动日志里明确标注 “哪个类触发了启动”,方便排查问题。
- 上下文关联:某些自动配置(比如
SpringApplicationAdminJmxAutoConfiguration
)会基于主类生成 MBean 名称,用于 JMX 监控。 - 框架扩展:第三方库(如 APM 工具、自定义启动器)可能需要获取主类,做链路追踪、启动增强等。
那么问题来了,deduceMainApplicationClass
方法是如何找到其主类的呢?
核心逻辑分两步:
用
StackWalker
遍历调用栈StackWalker
是 Java 9+ 引入的 API,能高效遍历当前线程的调用栈,且支持保留类引用(RETAIN_CLASS_REFERENCE
选项)。StackWalker
是专门为 高效遍历栈帧 设计的 API,通过StackFrame.getDeclaringClass()
直接获取Class
,无需反射。这让 Spring Boot 的启动流程更高效、更简洁。walk(this::findMainClass)
:把调用栈交给findMainClass
方法处理,找到符合条件的类。
findMainClass
筛选主类findMainClass
是一个方法引用,实际逻辑是遍历调用栈,找 包含main
方法的类:1
2
3
4
5
6
7
8
9private Optional<Class<?>> findMainClass(Stream<StackWalker.StackFrame> stackFrames) {
return stackFrames
// 过滤出包含 "main" 方法的栈帧
.filter(frame -> frame.getMethodName().equals("main"))
// 提取对应的类
.map(StackWalker.StackFrame::getDeclaringClass)
// 只保留第一个匹配的(因为 main 方法唯一)
.findFirst();
}调用栈里,
main
方法所在类的栈帧会包含methodName = "main"
,通过过滤、提取、去重,就能定位到主应用类。
所以说,虽然 Spring Boot 能自动推断主类,但某些场景(比如框架封装、多主类启动)可能需要手动指定。可以通过以下方式实现:
1 | SpringApplication app = new SpringApplication(MyMainClass.class); |
这种方式会跳过 deduceMainApplicationClass
的自动推断,强制使用指定的类作为主应用类
所以说,deduceMainApplicationClass
是 Spring Boot
启动流程的 “收尾环节”,通过 StackWalker
遍历调用栈,精准定位到 main
方法所在类。它的设计既保证了
高效性(避免全栈遍历),又能
直接关联类引用(无需反射),让主类识别逻辑简洁又可靠
Spring Boot 应用程序的启动与生命周期加载机制
这一部分,如果你没有看 Spring Boot part18-生命周期监听与9大事件 ,那就别往下看了
在上面,我们提到了如下内容
@SpringBootApplication
- 就是:
@Configuration
,容器中的组件,配置类。spring ioc 启动就会加载创建这个类对象
@EnableAutoConfiguration
: 开启自动配置
@AutoConfigurationPackage
:
扫描主程序包:加载自己的组件
- 利用
@Import(AutoConfigurationPackages.Registrar.class)
想要给容器中导入组件。 - 把主程序所在的包的所有组件导入进来。
- 为什么 SpringBoot 默认只扫描主程序所在的包及其子包
@Import(AutoConfigurationImportSelector.class)
:加载所有自动配置类:加载
starter 导入的组件
1 | List<String> configurations = ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()) |
扫描 SPI 文件:
1 | META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports |
@ComponentScan
组件扫描:排除一些组件(哪些不要)
排除前面已经扫描进来的 配置类、和 自动配置类 。
1 |
当调用 SpringApplication.run(MyApp.class, args)
时,核心流程如下:
1 | public ConfigurableApplicationContext run(String... args) { |
我们在这之后,就在 part18 详细展开讲了 Spring Boot 生命周期过程,其中 ioc 容器刷新部分也分为12小步,前些步都是工厂的内容,后面步骤都是其中处理组件的内容
1 |
|
进去到invokeBeanFactoryPostProcessors(beanFactory);
中,在这里会提前的自动配置的功能导入进来,这里也是加载spi的时机,相当于导入了自动配置,所有的自动配置类都是在这一步自动扫描进去的,这里只是扫描,创建还在下面finishBeanFactoryInitialization(beanFactory)

在finishBeanFactoryInitialization(beanFactory)
中,完成了组件的最终创建和初始化,相关代码如下
1 | String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false); |
- 提前初始化实现了
LoadTimeWeaverAware
接口的 Bean,这些 Bean 需要在类加载阶段进行 AOP 织入(例如 AspectJ 的 LTW)。通过getBean()
强制实例化这些 Bean,确保它们的LoadTimeWeaver
在类加载前准备就绪。
1 | beanFactory.preInstantiateSingletons(); |
- 遍历所有非懒加载的单例 Bean 定义,并按以下顺序创建它们:
- 优先创建实现了
PriorityOrdered
接口的 Bean。 - 再创建实现了
Ordered
接口的 Bean。 - 最后创建剩余的普通 Bean。
- 优先创建实现了
这样,我们的Spring Boot 生命周期过程就和自动配置,启动过程全部连接起来了
