Spring Boot启动慢如何优化

21 阅读9分钟

视频内容摘要:Spring Boot 启动如何从 90 秒优化到 15 秒以内

摘要说明:本摘要基于视频画面字幕与关键帧整理。视频主题是 Spring Boot 应用启动耗时优化,核心观点是:不要一上来盲目“加内存、换 SSD、改启动参数”,而是先用工具定位启动链路中真正耗时的阶段,再针对性优化。


1. 视频核心结论

这个视频讲的是一个典型面试题/项目优化题:

Spring Boot 应用启动很慢,比如 90 秒,如何优化到 15 秒以内?

视频给出的答案不是直接罗列优化项,而是强调:

  1. 先诊断,再优化:先确认慢在哪里,是 Bean 初始化慢、数据源连接慢,还是启动后置任务阻塞。
  2. 用启动耗时数据说话:通过 Spring Boot Actuator 的 /actuator/startup 或 APM 查看各阶段耗时。
  3. 针对大头优化:重点处理 Bean 扫描/初始化、数据源连接池初始化、ApplicationRunner/监听器同步任务。
  4. 最终优化链路:案例中启动时间大致从 78 秒 → 55 秒 → 28 秒 → 18 秒 → 12 秒,达到 15 秒以内。

2. 面试回答思路

错误回答示例

视频开头提到,一些候选人会直接回答:

  • 加内存
  • 换 SSD
  • 减少启动参数
  • 减少一些 @Component 扫描

这些回答不是完全错,但问题在于:没有先定位瓶颈,属于拍脑袋优化

推荐回答框架

更好的回答应该是:

我不会直接说改哪个配置,而是先接入启动耗时分析,比如 Spring Boot Actuator 的 /actuator/startup 或 APM,确认启动慢到底慢在 Bean 初始化、数据源初始化、配置加载,还是自定义 Runner/Listener。然后针对耗时最大的阶段逐项优化。


3. 启动耗时诊断方式

视频建议使用:

Spring Boot Actuator /actuator/startup

它可以看到 Spring Boot 启动过程中各阶段的耗时,帮助判断瓶颈。

视频案例中看到的耗时大致是:

阶段耗时问题说明
Bean 初始化52 秒Bean 数量太多,启动时全部实例化
数据源连接18 秒连接池启动时预创建数据库连接
自定义 ApplicationRunner6 秒启动时同步加载配置文件,阻塞主线程
其他2 秒其他启动开销

这个数据说明:Bean 初始化是最大头,占了绝大部分时间


4. 第一类优化:减少不必要的 Bean 扫描与初始化

问题原因

Spring Boot 启动时会扫描并初始化大量组件,例如:

  • @Component
  • @Service
  • @Configuration
  • @Repository

项目越大、依赖包越多、自动配置越多,启动时需要实例化的 Bean 就越多。

视频中的例子是:

当前项目是订单服务,但 pom 中引入了用户服务 SDK,SDK 里带了一堆 @Configuration,订单服务根本用不到这些 Bean,但启动时仍然会初始化。

优化方式

可以通过 @ComponentScanexcludeFilters 排除无关包或无关配置。

示例:

@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 秒

注意点

排除 Bean 不能盲目操作,需要确认:

  • 被排除的包确实不是当前服务必须依赖;
  • 不会误删核心 Configuration
  • 测试环境和预发环境需要完整回归;
  • 对公共 SDK 最好拆分 starter 或控制自动配置条件,而不是所有服务无差别加载。

5. 第二类优化:开启 Bean 懒加载

核心配置

spring.main.lazy-initialization=true

原理

开启懒加载后,Bean 不再全部在应用启动阶段创建,而是在第一次真正使用时才创建。

视频中的类比是:

餐厅开张时,不是一开门就把 100 道菜全部做好,而是客人点什么再做什么。

视频中的优化效果

开启懒加载后,大量非启动必要 Bean 被推迟初始化:

阶段启动时间
排除无关 Bean 后55 秒
开启懒加载后28 秒

重要风险:循环依赖可能延迟暴露

视频特别强调了懒加载的坑:

如果 Bean 之间存在循环依赖,懒加载可能不会在启动时报错,而是在某个接口第一次被调用时才报错,这比启动时报错更危险。

原因是:

  • 正常启动时,Spring 会在 Bean 创建过程中发现循环依赖;
  • 懒加载模式下,很多 Bean 启动时没有创建;
  • 依赖问题可能被推迟到运行时才暴露。

正确做法

  1. 先关闭懒加载;
  2. 正常启动一次;
  3. 检查日志里有没有循环依赖告警;
  4. 有问题先解决循环依赖;
  5. 再开启懒加载。

6. 第三类优化:调整 HikariCP 连接池初始化

问题原因

视频中提到:

HikariCP 默认启动时就会创建连接池,比如 minimum-idle 默认值为 10,意味着启动阶段立刻创建 10 个数据库连接。如果数据库响应慢,比如跨机房、网络抖动,这一步就可能拖慢几秒甚至十几秒。

优化配置

spring.datasource.hikari.minimum-idle=0

含义是:

启动时不提前创建空闲连接,第一个请求来了再按需创建连接。

视频中的优化效果

阶段启动时间
开启懒加载后28 秒
调整 HikariCP 后18 秒

注意点

这个配置适合启动速度敏感的服务,但需要结合业务判断:

  • 如果服务刚启动就有高并发流量,完全不预热连接可能导致首批请求略慢;
  • 可以结合预热接口、灰度发布、就绪探针来平衡启动速度和首个请求耗时;
  • 如果数据库连接慢的根因是网络、DNS、跨机房,也应该同步排查基础设施问题。

7. 第四类优化:ApplicationRunner / 启动监听器异步化

问题原因

视频中最后发现:

有一个 ApplicationRunner 在启动时加载本地配置文件,执行读文件、解析、校验等操作,耗时 6 秒。但这份配置不是接口启动必须依赖的。

如果这些逻辑写在:

  • ApplicationRunner
  • CommandLineRunner
  • ApplicationListener
  • @PostConstruct

并且同步执行,就会阻塞 Spring Boot 主启动线程。

优化方式

将非核心路径的启动任务改成异步执行。

示例:

@EnableAsync
@Configuration
public class AsyncConfig {
}
@Component
public class LocalConfigWarmupRunner implements ApplicationRunner {

    private final LocalConfigService localConfigService;

    public LocalConfigWarmupRunner(LocalConfigService localConfigService) {
        this.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 秒

8. 完整优化链路复盘

视频中的完整优化链路可以总结为:

步骤优化动作启动耗时变化
0初始状态,存在大量 Bean、数据源预连接、Runner 同步阻塞78 秒
1排除无关 SDK / 无关配置类,减少 Bean 数量78 秒 → 55 秒
2开启 spring.main.lazy-initialization=true55 秒 → 28 秒
3设置 spring.datasource.hikari.minimum-idle=028 秒 → 18 秒
4将非必要 ApplicationRunner 改为异步执行18 秒 → 12 秒

最终启动耗时进入 15 秒以内


9. 可以沉淀成企业级启动优化规范

9.1 启动耗时必须可观测

建议接入:

  • Spring Boot Actuator /actuator/startup
  • APM 启动链路追踪
  • 应用启动日志分段耗时打印
  • Bean 数量统计
  • 数据源初始化耗时统计
  • Runner / Listener 执行耗时统计

示例代码:

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

9.2 公共 SDK 不要无脑自动装配

公共 SDK、starter、组件包应该遵循:

  • 按需引入;
  • 按条件装配;
  • 不要让订单服务加载用户服务、营销服务、报表服务的一堆无关 Bean;
  • 对自动配置类加条件注解。

示例:

@Configuration
@ConditionalOnProperty(prefix = "user.sdk", name = "enabled", havingValue = "true")
public class UserSdkAutoConfiguration {
}

9.3 启动任务必须分级

可以把启动任务分成三类:

类型是否阻塞启动示例
核心任务必须阻塞初始化路由、加载核心配置、校验关键依赖
可延迟任务不应阻塞缓存预热、非核心字典加载、本地文件预解析
后台任务异步执行定时报表初始化、历史数据扫描、非实时同步

原则:

接口可用之前必须完成的任务才允许阻塞启动;其他任务尽量异步化或延迟执行。


10. 面试版标准回答

如果面试官问:

Spring Boot 启动 90 秒,如何优化到 15 秒?

可以这样回答:

我会先做启动耗时诊断,而不是直接盲目调参数。比如接入 Spring Boot Actuator 的 /actuator/startup 或 APM,看启动时间主要消耗在哪些阶段。常见大头有三个:第一是 Bean 扫描和初始化,第二是数据源连接池初始化,第三是 ApplicationRunner、CommandLineRunner 或监听器里同步执行了耗时任务。

如果发现 Bean 初始化很慢,我会先排查是否引入了无关 SDK 或无关 @Configuration,通过拆分 starter、条件装配或 @ComponentScan excludeFilters 减少无效 Bean。然后根据业务情况开启 spring.main.lazy-initialization=true,把非必要 Bean 延迟到首次使用时再初始化,但开启前要先检查循环依赖,避免问题延迟到运行时暴露。

如果数据源初始化慢,我会检查 HikariCP 连接池配置,例如将 spring.datasource.hikari.minimum-idle 调低甚至设为 0,避免启动阶段预创建大量连接。最后检查 ApplicationRunnerCommandLineRunnerApplicationListener 中是否有读文件、远程调用、缓存预热等非核心操作,把它们改成异步执行或延后执行。

整体思路是:先可观测定位瓶颈,再按 Bean、数据源、启动任务三个方向逐项优化,并用数据验证优化效果。


11. 一句话总结

这个视频真正想表达的是:

Spring Boot 启动优化不是背配置,而是先定位耗时,再针对 Bean 初始化、数据源连接、启动任务阻塞这三个大头逐步优化。