这是《Spring Boot 与 Spring Cloud 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。
SpringApplication.run 看似只有一行,实际完成了环境准备、上下文创建、Bean 加载、内嵌服务器启动和生命周期回调。掌握这条链路,可以定位启动失败、启动慢、配置未生效、端口冲突、优雅停机等常见问题。
7.1 启动入口
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
也可以显式定制:
SpringApplication app = new SpringApplication(OrderApplication.class);
app.setApplicationType(ApplicationType.SERVLET);
app.setBannerMode(Banner.Mode.OFF);
app.setWebApplicationType(WebApplicationType.SERVLET);
app.addListeners(new StartupListener());
app.run(args);
7.2 SpringApplication 构造阶段
构造 SpringApplication 时会推断:
- 应用类型:Servlet、Reactive、普通应用;
- 初始化器;
- 监听器;
- 主类。
判断依据来自 classpath:
存在 Servlet 类 -> SERVLET
存在 Reactive 相关类且不存在 Servlet -> REACTIVE
都不存在 -> NONE
因此引入或移除依赖可能改变上下文类型,进而影响自动装配结果。
7.3 run 主线
简化流程:
1. StopWatch 计时
2. 获取 SpringApplicationRunListeners
3. prepareEnvironment
4. configureIgnoreBeanInfo
5. printBanner
6. createApplicationContext
7. prepareContext
8. refreshContext
9. afterRefresh
10. listeners.started
11. callRunners
12. listeners.ready
关键扩展点:
| 阶段 | 扩展 |
|---|---|
| 环境准备后 | ApplicationEnvironmentPreparedEvent |
| 上下文准备 | ApplicationContextInitializer |
| 上下文加载 | ApplicationPreparedEvent |
| 刷新完成 | ApplicationStartedEvent |
| Runner 前 | ApplicationRunner / CommandLineRunner |
| 完全就绪 | ApplicationReadyEvent |
7.4 环境准备
ApplicationArguments
-> ConfigurableEnvironment
-> configureEnvironment
-> 配置 conversion service
-> 绑定 spring.main
-> EnvironmentPostProcessor
常见配置源会进入 Environment:
- 命令行属性;
- 系统属性;
- 环境变量;
- application.yml;
- profile 文件; 6.配置中心扩展的 PropertySource。
Spring Boot 的配置文件加载由 ConfigDataEnvironmentPostProcessor 主导。自定义 EnvironmentPostProcessor 可以补充属性源:
public class SecurePropertyEnvironmentPostProcessor implements EnvironmentPostProcessor {
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
Map<String, Object> props = Map.of("app.secret-source", "vault");
environment.getPropertySources().addFirst(
new MapPropertySource("secureProperties", props));
}
}
注册文件:
src/main/resources/META-INF/spring.factories
org.springframework.boot.env.EnvironmentPostProcessor=\
com.example.SecurePropertyEnvironmentPostProcessor
Spring Boot 3 中部分机制从 spring.factories 迁移到不同文件,需按当前版本文档注册。
7.5 上下文创建与刷新
Servlet 应用通常创建:
AnnotationConfigServletWebServerApplicationContext
refreshContext 会执行 Spring 容器 refresh,并在 onRefresh 阶段创建 WebServer。
ServletWebServerApplicationContext
-> onRefresh
-> createWebServer
-> TomcatServletWebServerFactory
-> getTomcatWebServer
容器启动时机:
| 阶段 | 状态 |
|---|---|
| createWebServer | 服务器对象创建 |
| finishRefresh | WebServer 启动 |
| ApplicationReadyEvent | 应用完全就绪 |
端口绑定失败通常发生在服务器启动阶段。
7.6 应用事件监听
@Component
public class StartupEventListener {
@EventListener
public void onReady(ApplicationReadyEvent event) {
System.out.println("application ready");
}
}
注意:@Component 监听器通常在 Bean 初始化后才能接收后续事件。如果要在环境准备、上下文创建前监听,需要通过 SpringApplication.addListeners 或注册文件添加全局 Listener。
事件顺序:
ApplicationStartingEvent
ApplicationEnvironmentPreparedEvent
ApplicationPreparedEvent
ApplicationStartedEvent
ApplicationReadyEvent
关闭时:
ContextClosedEvent
7.7 Runner
@Component
public class CacheWarmUpRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) {
warmUp();
}
}
执行时机在 ApplicationStartedEvent 之后、ApplicationReadyEvent 之前。因此 Runner 阻塞会推迟就绪时间。
建议:
- 只做必要初始化;
- 慢速预热异步执行;
- 外部调用设置超时;
- 记录启动阶段耗时;
- 失败策略明确:阻止启动还是降级运行。
7.8 启动耗时分析
开启启动诊断:
logging.level.org.springframework.boot.context.config=DEBUG
Java Flight Recorder:
java -XX:StartFlightRecording=filename=startup.jfr,settings=profile \
-jar app.jar
常见慢点:
| 慢点 | 现象 |
|---|---|
| 类扫描过宽 | 主类放在根包外或扫描 com.* |
| Bean 过多 | 大量自动装配和第三方客户端 |
| 初始化远程调用 | Runner 中同步加载配置 |
| 数据库连接 | 初始化连接池或执行迁移 |
| CPU limit 低 | JIT 和类加载慢 |
| 磁盘慢 | 日志、证书、本地文件读取 |
| 依赖冲突 | 反复解析和失败重试 |
Spring Boot 也可以输出 Startup Endpoint:
/actuator/startup
该端点依赖 BufferingApplicationEventPublisher,并需要安全保护。
7.9 常见启动失败
| 异常 | 方向 |
|---|---|
| Port already in use | 端口冲突 |
| UnsatisfiedDependencyException | Bean 缺失或循环依赖 |
| InvalidConfigurationPropertyException | 配置绑定错误 |
| Cannot determine embedded database driver | 缺数据库依赖或 URL |
| ClassCastException | 依赖版本冲突 |
| NoSuchMethodError | 编译期与运行期版本不一致 |
| FileNotFoundException | 外部配置或证书路径错误 |
| Unable to load ApplicationContext | 通常看 caused by |
排查原则:
读完整 caused by
-> 确认版本矩阵
-> 查看自动装配报告
-> 最小化复现工程
开启自动装配报告:
java -jar app.jar --debug
7.10 嵌入式服务器选择
常见服务器:
| 服务器 | 特点 |
|---|---|
| Tomcat | Spring Boot 默认,生态成熟 |
| Jetty | 轻量,传统支持广泛 |
| Undertow | 常用于追求低内存和高吞吐场景 |
排除 Tomcat 并引入 Jetty:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
选择应基于压测,而不是传闻。
本章小结
Spring Boot 启动流程由 SpringApplication 引导,完成应用类型推断、环境准备、上下文创建、容器刷新、内嵌服务器启动和 Runner 执行。ApplicationReadyEvent 才是完全就绪信号。排查启动问题要抓住完整异常链、事件阶段和自动装配报告,优化启动时要区分必要初始化和可异步预热。
思考题
ApplicationStartedEvent和ApplicationReadyEvent有什么区别?- 为什么 Runner 中不宜执行耗时预热?
- 内嵌服务器是在 refresh 的哪个阶段创建的?
- 如何分析 Spring Boot 启动慢?
- 引入依赖为什么可能改变应用类型?