发布于2026-06-30 阅读(0)
扫一扫,手机访问
关于Spring Boot应用启动慢的优化,市面上有很多现成的方案,比如加内存、换SSD、改启动参数。这些方法不能说错,但它们更像是“头痛医头”的补救措施。本文要讨论的思路其实更根本:在动手优化之前,得先搞清楚“慢”在哪儿,用数据说话,然后瞄准“大头”来干,才能事半功倍。

今天聊的问题很经典:一个Spring Boot应用启动要90秒,怎么优化到15秒以内?
回答这个问题的关键,不是立刻背出所有优化项,而是先亮出你的诊断思路。
/actuator/startup或APM工具,把启动过程的各个阶段耗时拉出来看看。ApplicationRunner,针对性地优化。从实战效果来看,经过这样一系列优化,启动时间完全可以做到从接近80秒一路降到12秒,达成15秒以内的目标。
面试时,如果问你这个问题,哪些回答是“低分”的?就是那些没经过思考的选项:
这些回答的问题在于,都是“拍脑袋”式优化,没有先定位瓶颈。好比车坏了,你不管三七二十一先把轮胎换了,结果可能是发动机没油了。
一个真正专业的回答应该是这样的:
我不会直接说改哪个配置。我会先接入启动耗时分析,比如用Spring Boot Actuator的/actuator/startup或APM,确认瓶颈到底在Bean初始化、数据源初始化、配置加载,还是自定义的Runner或Listener上。然后,针对耗时最大的阶段逐个击破。
那么,具体怎么诊断呢?最简单直接的方式是使用Spring Boot Actuator提供的/actuator/startup端点。
一个典型的诊断结果可能会看到这样的耗时分布:
| 阶段 | 耗时 | 问题说明 |
|---|---|---|
| Bean初始化 | 52秒 | Bean数量太多,启动时全部实例化 |
| 数据源连接 | 18秒 | 连接池启动时预创建数据库连接 |
| 自定义ApplicationRunner | 6秒 | 启动时同步加载配置文件,阻塞主线程 |
| 其他 | 2秒 | 其他启动开销 |
看到没?Bean初始化占了绝对大头,把这52秒搞定,优化就完成了大半。
问题:项目越大,依赖越多,自动配置越复杂,启动时Spring需要实例化的Bean就越多。最典型的场景是:
你的项目是订单服务,但pom里引入了用户服务SDK。这个SDK自带了一堆@Configuration,而你的订单服务根本用不到这些Bean。但Spring启动时,还是会“好心”地把它们全部初始化。
优化:通过@ComponentScan的excludeFilters,明确排除掉那些对当前服务无用的包或配置。
一个具体的例子:
@SpringBootApplication
@ComponentScan(
basePackages = "com.example",
excludeFilters = {
@ComponentScan.Filter(
type = FilterType.REGEX,
pattern = "com.example.user.sdk.*"
)
}
)
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
效果:
| 优化前 | 优化后 |
|---|---|
| Bean数量约1200个 | Bean数量约800个 |
| 启动时间78秒 | 启动时间55秒 |
注意:排除操作需要非常谨慎,务必确认:
Configuration。配置:
spring.main.lazy-initialization=true
原理:这个配置就像餐厅开张,不是一开门就把100道菜都做好,而是客人点什么,后厨再做什么。开启后,启动时只初始化必须的Bean,其余的在第一次被使用时才会创建。
效果:
| 阶段 | 启动时间 |
|---|---|
| 排除无关Bean后 | 55秒 |
| 开启懒加载后 | 28秒 |
效果拔群,但有一个巨大的坑必须警惕:循环依赖问题会从启动时暴露,拖延到运行时暴露。
简单来说,正常启动时,Spring在创建Bean的过程中就能发现循环依赖并报错。但懒加载模式下,很多Bean在启动时没有创建,依赖关系没有被触发,直到第一次调用接口时才报错。这个运行时错误,远比启动时报错麻烦。
正确的做法是:
问题:HikariCP连接池默认会在启动时预创建连接。比如minimum-idle默认值为10,意味着启动阶段就要创建10个数据库连接。如果数据库响应慢(比如跨机房、网络抖动),这一步就可能拖慢几秒甚至十几秒。
优化:将minimum-idle设置为0,让连接池启动时不创建空闲连接,第一个请求来了再按需创建。
spring.datasource.hikari.minimum-idle=0
效果:
| 阶段 | 启动时间 |
|---|---|
| 开启懒加载后 | 28秒 |
| 调整HikariCP后 | 18秒 |
注意:这个配置对启动速度敏感的服务非常有效。但需要权衡,如果服务刚启动就有大量并发请求,没有预热连接可能导致首批请求变慢。可以通过预热接口、灰度发布、就绪探针等手段来平衡。另外,如果数据库连接慢的根因是网络、DNS或跨机房问题,也建议同步排查基础设施。
问题:最后,诊断发现一个ApplicationRunner在启动时加载本地配置文件,耗时6秒。但这份配置不是接口启动所必需的。
很多开发者习惯将一些初始化逻辑写在ApplicationRunner、CommandLineRunner、ApplicationListener或@PostConstruct中,并且默认是同步执行的。这会阻塞Spring Boot的主启动线程。
优化:将非核心路径的启动任务改为异步执行。
示例:
@EnableAsync
@Configuration
public class AsyncConfig {}
@Component
public class LocalConfigWarmupRunner implements ApplicationRunner {
private final LocalConfigService localConfigService;
// 构造函数注入...
@Async
@Override
public void run(ApplicationArguments args) {
localConfigService.loadAndValidate();
}
}
更稳妥的企业级做法,是使用独立的线程池来执行这些异步任务:
@Configuration
@EnableAsync
public class AsyncExecutorConfig {
@Bean("startupTaskExecutor")
public Executor startupTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(4);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("startup-task-");
executor.initialize();
return executor;
}
}
@Async("startupTaskExecutor")
public void loadAndValidate() {
// 读取文件、解析、校验、缓存预热等非核心启动任务
}
效果:
| 阶段 | 启动时间 |
|---|---|
| 调整数据源后 | 18秒 |
| Runner异步化后 | 12秒 |
我们来复盘一下整个优化链路:
| 步骤 | 优化动作 | 启动耗时变化 |
|---|---|---|
| 0 | 初始状态:大量Bean、数据源预连接、Runner同步阻塞 | 78秒 |
| 1 | 排除无关SDK/配置,减少Bean数量 | 78秒 → 55秒 |
| 2 | 开启懒加载 | 55秒 → 28秒 |
| 3 | 设置minimum-idle=0 | 28秒 → 18秒 |
| 4 | 非必要Runner异步化 | 18秒 → 12秒 |
这篇文章核心想传达的,其实不止是几个配置参数,而是可以指导团队实践的优化规范。
第一,启动耗时必须可观测。
接入一些基础工具是很有必要的,例如:
/actuator/startup。代码层面,可以这样开启启动信息记录:
public static void main(String[] args) {
SpringApplication application = new SpringApplication(OrderApplication.class);
application.setApplicationStartup(new BufferingApplicationStartup(2048));
application.run(args);
}
同时,在Actuator配置中暴露相关信息:
management.endpoints.web.exposure.include=startup,health,info
第二,公共SDK不要“无脑”自动装配。
公共SDK、starter、组件包应该遵循“按需引入”、“条件装配”的原则。不能让一个订单服务去加载用户服务、营销服务等一堆无关的Bean。对自动配置类加上条件注解是非常好的实践:
@Configuration
@ConditionalOnProperty(prefix = "user.sdk", name = "enabled", ha vingValue = "true")
public class UserSdkAutoConfiguration {
}
第三,启动任务必须分级。
可以根据业务重要性,把启动任务分成三类:
| 类型 | 是否阻塞启动 | 示例 |
|---|---|---|
| 核心任务 | 必须阻塞 | 初始化路由、加载核心配置、校验关键依赖 |
| 可延迟任务 | 不应阻塞 | 缓存预热、非核心字典加载、本地文件预解析 |
| 后台任务 | 异步执行 | 定时报表初始化、历史数据扫描、非实时同步 |
原则很明确:接口可用之前必须完成的任务,才允许阻塞启动;其他任务,能异步就异步,能延迟就延迟。
如果面试官问你:“Spring Boot启动90秒,如何优化到15秒?”你可以这样回答:
“我会先做启动耗时诊断,而不是直接调参数。比如接入Spring Boot Actuator的/actuator/startup或APM,看启动时间主要消耗在哪些阶段。常见的大头有三个:Bean扫描和初始化、数据源连接池初始化、以及ApplicationRunner/CommandLineRunner或监听器里同步执行的耗时任务。
如果Bean初始化慢,我会排查是否引入了无关SDK或@Configuration,通过条件装配或@ComponentScan excludeFilters减少无效Bean。然后根据业务情况开启懒加载,但开启前必须先检查循环依赖。
如果数据源初始化慢,我会调整HikariCP连接池的minimum-idle配置,避免启动阶段预创建大量连接。最后,检查所有Runner和Listener,把非核心的耗时操作改成异步或延后执行。
整体思路就是:先观测、再定位、后优化,确保每一步都有数据支撑。”
Spring Boot启动优化的核心,不是背几个配置项,而是先定位“慢”在哪里,然后针对Bean初始化、数据源连接、启动任务阻塞这三大头,手起刀落,精准优化。
下一篇:java实现猜数字游戏教程
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8