终于解决了!Spring Boot 启动慢的 5 个优化点

针对 1.4/1.5 老版本,手把手教你把启动时间缩短 50%+

引言:还在等 Spring Boot 启动?可能是你没优化对

很多团队的核心业务系统还在使用 Spring Boot 1.4/1.5 这类早期经典版本:

  • 代码稳定、依赖体系成熟、不敢轻易大版本升级;
  • 但一个启动 30 秒甚至 1 分钟的老项目,严重拖累日常开发调试与部署效率。

实际排查下来,大部分启动慢问题,并不是业务逻辑本身慢,而是:

  • 自动配置加载了一堆用不到的组件;
  • 包扫描范围过大;
  • JVM 参数不合理;
  • 非核心 Bean 提前初始化;
  • 冗余依赖和插件拖后腿。

本文结合老项目实战经验,总结了 5 个几乎“零重构”的优化点

  • 不改业务代码、不推翻架构,在原有基础上就能把启动时间轻松缩短 50%+;
  • 特别适用于:老项目维护、开发环境提速、生产部署优化等场景。

一、优化点 1:禁用不必要的自动配置(核心)

1.1 问题原因

在 Spring Boot 1.4/1.5 中,spring-boot-autoconfigure 会根据类路径和配置自动装配大量组件,比如:

  • 数据源(多数据源、JPA、事务)
  • Redis、缓存、消息中间件
  • 邮件发送、模板引擎、Actuator 等

问题在于:只要依赖在类路径上,Spring Boot 就会尝试自动配置 Bean
即使你项目中完全没用到这些功能,也会:

  • 扫描大量配置类;
  • 创建并初始化对应的 Bean;
  • 甚至还会尝试连数据库、拉起连接池。

最终结果:启动阶段 Bean 初始化数量暴增,耗时显著上升

1.2 操作步骤:通过 exclude 精准禁用无用自动配置

步骤 1:打开 debug 日志,查看生效的自动配置

在 application.properties 中增加:

properties

debug=true

启动后控制台会输出类似:

text

========================= AUTO-CONFIGURATION REPORT ========================= Positive matches: DataSourceAutoConfiguration matched ... RedisAutoConfiguration matched ... MailSenderAutoConfiguration matched ... ...

Positive matches 部分就是当前真正生效的自动配置列表
结合业务实际,挑出确定 完全不用 的配置类。

步骤 2:使用 @SpringBootApplication(exclude = …) 排除

在主启动类上排除这些自动配置,例如:

java

import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; import org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration; @SpringBootApplication( exclude = { DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class, RedisAutoConfiguration.class, MailSenderAutoConfiguration.class } ) public class LegacyApplication { public static void main(String[] args) { SpringApplication.run(LegacyApplication.class, args); } }

如果你只在某些 profile 下需要这些配置,也可以配合 @ConditionalOnProperty 等方式做更细粒度控制,这里先给出最直接的方式。

1.3 原理说明

Spring Boot 启动过程中的大头工作包括:

  • 扫描 XXXAutoConfiguration 类;
  • 解析条件注解(@ConditionalOnClass、@ConditionalOnMissingBean 等);
  • 注册对应的 BeanDefinition,实例化并注入依赖。

通过 exclude 排除无用自动配置:

  • 这些自动配置类根本不会被处理;
  • 对应的 Bean 不会被创建,不再参与依赖注入与生命周期管理;
  • 启动阶段整体要初始化的 Bean 总数明显减少,从而大幅压缩启动时间。

1.4 注意事项

⚠️ 注意:排除前必须确认业务确实不依赖该组件

如果误排除正在使用的数据源、事务或缓存相关自动配置,可能出现启动失败或运行时异常; 生产环境中建议先在测试环境验证: 启动是否正常; 核心业务回归是否通过。


二、优化点 2:缩小包扫描范围

2.1 问题原因

Spring Boot 默认会:

  • 扫描主类所在包及其子包下的所有类
  • 包括 Controller、Service、Repository、配置类、工具类、甚至某些测试或无用类。

大型老项目中,经常出现这种情况:

  • com.company.project 作为顶级包,下面挂了大量历史包结构;
  • 很多模块早已弃用,但类文件仍然存在;
  • 部分第三方集成 demo 代码也被一起扫描。

结果是:类路径扫描和 Bean 定义解析工作量明显增大,启动开销增加。

2.2 操作步骤:精确指定需要扫描的业务包

步骤 1:使用 scanBasePackages 明确核心业务包

在主启动类中指定扫描范围,例如:

java

import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication( scanBasePackages = { "com.company.project.api", "com.company.project.service", "com.company.project.repository", "com.company.project.config" } ) public class LegacyApplication { public static void main(String[] args) { SpringApplication.run(LegacyApplication.class, args); } }

这样,Spring 只会在上述几个包中查找:

  • @Controller / @RestController
  • @Service / @Component
  • @Repository
  • 以及其他 Spring 管理的 Bean。

步骤 2:对非核心模块使用 @Import 手动引入

如果某些第三方集成、独立模块不想被大范围扫描,可以:

  • 将其配置类从扫描路径中移除;
  • 使用 @Import 在主配置类中显式导入

示例:

java

import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @Configuration @Import({ ThirdPartyDataSourceConfig.class, ExternalJobSchedulerConfig.class }) public class ExtraConfig { }

主启动类仍然只扫描业务核心包,而额外的集成模块通过 @Import 精准引入。

2.3 原理说明

包扫描阶段主要做两件事情:

  • 扫描所有类文件、解析注解元数据;
  • 为符合条件的类注册 BeanDefinition。

缩小扫描范围带来的好处:

  • 扫描的类文件数量大幅减少;
  • 无用的测试类、demo 类不会再参与 Bean 解析;
  • 减少 I/O 和反射操作;
  • 间接减少自动配置和条件匹配的触发次数。

2.4 注意事项

⚠️ 注意:扫描包配置不当会导致 Bean 漏扫

如果某些核心业务 Bean 所在包没被包含进来,会出现启动失败、依赖找不到、Controller 不生效等问题; 调整扫描范围后,建议: 全量跑一次关键接口回归; 对常用 URL 做一次冒烟测试。


三、优化点 3:优化 JVM 启动参数(适配 JDK 8)

3.1 问题原因

Spring Boot 1.4/1.5 多数运行在 JDK 8 上,如果 JVM 参数不合理,会导致:

  • 内存分配频繁、Full GC 频繁;
  • 类验证、JIT 预热过程缓慢;
  • 导致整体启动时间被 JVM 本身拖慢。

老项目常见问题:

  • 使用默认 JVM 参数;
  • 线上、线下参数混用;
  • 误用了不适配版本的 GC 策略。

3.2 推荐 JVM 参数示例(JDK 8)

开发环境(以启动速度优先)

bash

java \ -Xms512m \ -Xmx512m \ -XX:MetaspaceSize=128m \ -XX:MaxMetaspaceSize=256m \ -XX:+UseCompressedOops \ -XX:+UseG1GC \ -XX:MaxGCPauseMillis=200 \ -XX:+PrintGCDetails \ -XX:+PrintGCDateStamps \ -Xloggc:logs/gc.log \ -Xverify:none \ -Dspring.profiles.active=dev \ -jar legacy-app.jar

关键参数说明:

  • -Xms/-Xmx:固定堆大小,避免频繁扩容;
  • MetaspaceSize/MaxMetaspaceSize:控制类元数据空间,防止过小频繁 GC;
  • UseG1GC:适合大部分服务场景,GC 暂停可控;
  • Xverify:none:关闭类验证,加快类加载(仅推荐开发环境)

生产环境(兼顾稳定性)

bash

java \ -Xms2g \ -Xmx2g \ -XX:MetaspaceSize=256m \ -XX:MaxMetaspaceSize=512m \ -XX:+UseCompressedOops \ -XX:+UseG1GC \ -XX:MaxGCPauseMillis=200 \ -XX:+PrintGCDetails \ -XX:+PrintGCDateStamps \ -Xloggc:/data/logs/gc.log \ -Dspring.profiles.active=prod \ -jar legacy-app.jar

生产环境建议:

  • 不使用 -Xverify:none,保证类验证安全性;
  • 堆大小根据服务负载适当增大,避免 OOM;
  • 保留 GC 日志,便于问题排查。

3.3 原理说明

JVM 启动阶段涉及:

  • 类加载、字节码验证;
  • JIT 编译预热;
  • 堆与元空间初始化。

合理配置 JVM 参数可以:

  • 减少类验证耗时(开发环境);
  • 避免因堆/元空间过小导致的频繁 GC;
  • 提高内存分配与回收效率,间接缩短启动时间。

3.4 注意事项

⚠️ 注意:-Xverify:none 不建议在生产环境使用

关闭类验证会略微提升启动速度,但也可能掩盖某些类加载问题; 生产环境应优先保证稳定性与安全性,仅在开发/测试环境使用此项。


四、优化点 4:延迟初始化非核心 Bean

4.1 问题原因

默认情况下,Spring 在启动时会:

  • 将所有单例 Bean 全部创建并初始化完毕;
  • 包括定时任务、异步服务、缓存预热、报表生成、第三方对接等。

对于很多老项目来说,这些 Bean:

  • 并非每次启动都立即用到;
  • 甚至只在某些功能入口才会首次访问。

但它们却在启动阶段“抢占时间”,导致整体启动明显偏慢。

4.2 操作步骤 1:全局延迟初始化(慎用)

在 application.properties 中配置:

properties

spring.main.lazy-initialization=true

Spring Boot 1.4/1.5 没有这个统一属性,可以通过 自定义 BeanFactoryPostProcessor 或升级到 2.x 后使用。
若你已做了小版本升级(如维护分支升级到 2.1/2.3),可以直接用此配置。
对于原生 1.4/1.5,可优先考虑“局部延迟初始化”。

4.3 操作步骤 2:局部延迟初始化(推荐)

非核心、非强依赖链上的 Bean 使用 @Lazy:

java

import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; @Service @Lazy public class ReportGenerateService { public void generateDailyReport() { // 生成报表的耗时逻辑 } }

也可以在依赖注入处使用:

java

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; @Service public class OrderService { @Autowired @Lazy private ReportGenerateService reportGenerateService; public void createOrder() { // 下单逻辑 // 只有真正需要生成报表时,才会初始化 ReportGenerateService } }

4.4 原理说明

  • 默认:单例 Bean 在容器刷新阶段一次性创建完毕;
  • 延迟初始化:Bean 第一次被访问时才创建。

将非核心 Bean 延迟初始化,可以:

  • 将原本启动阶段的初始化压力,拆散到运行期间;
  • 明显缩短“应用就绪时间”(从启动命令到可对外提供核心服务的时间);
  • 尤其适合一些“偶发调用”的后台任务、报表、同步 JOB 等。

4.5 注意事项

⚠️ 注意:全局延迟初始化可能隐藏启动期异常

某些 Bean 的配置错误,仅在其首次访问时才会抛出异常; 如果这些 Bean 是核心功能的一部分,可能在系统运行一段时间后才抛出问题; 建议: 优先对明确非核心的 Bean 使用 @Lazy; 若使用全局 lazy,务必在测试环境做一次“功能全量走一遍”的回归。


五、优化点 5:移除冗余依赖与插件

5.1 问题原因

老项目演进多年后,典型现象是:

  • POM 中堆满历史依赖:老日志框架、老数据库驱动、废弃组件;
  • 多个 starter 自动带了一堆间接依赖;
  • Maven 插件配置复杂,在打包/启动时执行不必要的任务。

这些冗余依赖和插件会导致:

  • 类路径膨胀、类加载数量暴增;
  • 自动配置命中更多分支;
  • 打包和启动过程执行多余检查或增强步骤。

5.2 操作步骤 1:用 dependency:tree 分析依赖

在项目根目录执行:

bash

mvn dependency:tree > dependency-tree.txt

打开 dependency-tree.txt,重点关注:

  • 未使用的 starter(如 spring-boot-starter-mail、spring-boot-starter-redis 等);
  • 重复版本依赖(多版本 commons-*);
  • 不再使用的中间件 client 包。

对于明确不需要的依赖,可在 pom.xml 中排除,例如:

1)排除内置 Tomcat(改用外置容器时):

xml

<dependencies> <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> <!-- 使用外部容器时可以去掉内嵌容器相关依赖 --> </dependencies>

2)排除不必要的自动配置模块:

xml

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> <exclusions> <exclusion> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> </exclusion> </exclusions> </dependency>

5.3 操作步骤 2:精简 spring-boot-maven-plugin 配置

典型的插件配置:

xml

<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>1.5.22.RELEASE</version> <configuration> <mainClass>com.company.project.LegacyApplication</mainClass> <!-- 如果不需要,关闭某些额外的 repackage 行为或自定义 layout --> </configuration> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> </plugins> </build>

建议:

  • 若未使用自定义 layout 或不依赖某些高级特性,不要再额外配置多余的 transformer;
  • 构建 pipeline 中避免在每次启动前都做 clean package,而是:开发环境更多使用 IDE 直接运行 main 方法

5.4 原理说明

  • 类路径上的 jar 越多,启动阶段的扫描、类加载、资源查找就越重;
  • 多余的 starter+自动配置会进一步放大自动装配的工作量;
  • 复杂的 Maven 插件在构建阶段做的工作越多,得到可运行包的时间就越长。

清理冗余依赖与插件的本质,就是减少“项目的体重”。

5.5 注意事项

⚠️ 注意:依赖移除/排除前要确认影响范围

使用 mvn dependency:analyze 辅助分析未使用依赖,但结果仅供参考; 移除或排除依赖后,请务必: 重新编译,确保无编译错误; 跑一遍基础集成测试,防止运行时 ClassNotFoundException。


六、优化效果验证:如何量化“到底快了多少”

做完优化,如果没有数据支撑,很难说服团队。这里给出两种简单实用的验证方式。

6.1 在 main 方法中添加启动计时日志

在主启动类中加入简单计时代码:

java

import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class LegacyApplication { public static void main(String[] args) { long start = System.currentTimeMillis(); SpringApplication.run(LegacyApplication.class, args); long end = System.currentTimeMillis(); System.out.println(">>> Application started in " + (end - start) + " ms <<<"); } }

每次启动时,控制台会打印:

text

>>> Application started in 12345 ms <<<

你可以:

  • 记录优化前后的多次启动耗时;
  • 在不同机器/环境下对比,得到一个相对可观的提升比例

6.2 使用 Spring Boot Actuator 分析 Bean 初始化耗时

如果项目中已集成 Actuator,可以启用相关端点。

1)POM 中引入依赖(如尚未添加)

xml

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>

2)在配置中开放必要端点(以 1.5.x 为例)

properties

management.security.enabled=false endpoints.metrics.enabled=true endpoints.beans.enabled=true

然后访问:

  • /metrics:查看应用层面的指标;
  • /beans:查看 Bean 列表,结合日志或监控记录启动阶段 Bean 创建的时间分布。

Tip: 在 2.x 中可以借助更多细粒度的指标(如 startup 相关指标)进行可视化分析。


七、总结与拓展

7.1 5 个优化点的实战优先级

在不做大规模重构、不升级大版本的前提下,可以按下面的优先级落地执行:

  1. 禁用不必要的自动配置(收益最高,改动小,立竿见影);
  2. 缩小包扫描范围(控制扫描粒度,防止历史垃圾代码拖累启动);
  3. 延迟初始化非核心 Bean(让启动阶段更“瘦身”,核心功能先跑起来);
  4. JVM 参数优化(中长期收益可观,需结合环境调优);
  5. 移除冗余依赖与插件(治理项目“技术债”,顺带给后续升级打基础)。

配合这 5 个优化点,实践中老项目启动时间从 30 秒+ 压缩到 10~15 秒 是完全可以做到的。

7.2 拓展建议

  • 逐步升级到 Spring Boot 2.x
    • 新版本在自动配置、启动性能、监控指标等方面都有明显优化;
    • 可考虑先引入兼容性分支,小步升级到 2.1/2.3 等长期维护版本。
  • 引入 Spring Boot DevTools 提升开发体验
    • 支持自动重启、静态资源热加载;
    • 与 IDE 配合使用,可以极大缩短“改代码→看效果”的闭环时间。

⚠️ 建议:老项目的性能治理,要把“启动时间优化”纳入整体技术债清单

与依赖升级、JDK 升级、日志体系重构等一起规划; 避免每次都是临时救火,而是有节奏地演进。

### 优化 Spring Boot 应用启动速度的方法 在微服务架构中,Spring Boot 应用的启动速度直接影响开发效率、持续集成速度以及线上服务的弹性能力。为了加快 Spring Boot 应用的启动速度,可以从以下几个方面入手: #### 1. 启用懒加载(Lazy Initialization) 通过启用懒加载,可以推迟某些 Bean 的初始化,直到它们首次被使用。这可以显著减少应用启动时的初始化时间。 - 在 `@SpringBootApplication` 注解中添加 `lazy = true` 属性: ```java @SpringBootApplication(lazy = true) public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } } ``` - 或者在 `application.properties` 中配置: ```properties spring.main.lazy-initialization=true ``` 启用懒加载后,Spring 容器会在需要时才初始化 Bean,从而减少启动时的初始化工作量[^5]。 #### 2. 优化依赖管理 检查并减少不必要的依赖项,尤其是那些在启动时不会使用的库。不必要的依赖会增加类路径扫描的时间,从而影响启动速度。 - 使用 `spring-boot-starter-web` 而不是 `spring-boot-starter-tomcat`,因为前者已经包含了后者,并且会减少一些默认依赖。 - 使用工具如 `mvn dependency:tree` 或 `gradle dependencies` 来分析依赖树,移除不必要的依赖。 优化依赖管理不仅可以减少启动时间,还可以降低应用的复杂性和潜在的冲突风险[^4]。 #### 3. 分析启动日志 通过分析 Spring Boot启动日志,可以识别哪些部分耗时较长。Spring Boot 提供了详细的启动日志,可以帮助定位性能瓶颈。 - 查看启动日志中的各个阶段耗时,重关注 `ApplicationStartingEvent` 到 `ApplicationReadyEvent` 之间的耗时。 - 使用 `SpringApplication.setLogStartupInfo(true)` 来启用更详细的启动信息输出。 分析启动日志可以帮助开发者快速定位问题所在,并针对性地进行优化[^1]。 #### 4. 使用 AOT(Ahead-Of-Time)编译 AOT 编译可以在构建时生成一些优化后的代码,从而减少运行时的初始化时间。Spring Boot 提供了对 AOT 编译的支持,可以通过 `spring-aot` 插件来启用。 - 添加 `spring-aot` 插件到 `pom.xml` 或 `build.gradle` 中。 - 配置插件以生成优化后的代码。 AOT 编译可以显著减少运行时的类加载和初始化时间,从而加快应用的启动速度[^2]。 #### 5. 使用 Native Image(原生镜像) Spring Boot 支持与 GraalVM 的 Native Image 工具集成,生成原生镜像。原生镜像可以在启动时直接运行,而不需要经过 JVM 的类加载和 JIT 编译过程,从而大幅缩短启动时间。 - 安装 GraalVM 并配置环境变量。 - 使用 `native-image-maven-plugin` 或 `native-image-gradle-plugin` 来生成原生镜像。 使用原生镜像可以将启动时间从几秒缩短到几百毫秒,非常适合需要快速启动的场景[^2]。 #### 6. 优化数据库连接池配置 如果应用使用了数据库连接池(如 HikariCP、Tomcat JDBC Pool 等),可以通过优化连接池的配置来减少启动时间。 - 减少初始连接数,避免在启动时建立过多的数据库连接。 - 配置合理的连接超时时间和最大连接数,确保连接池在需要时能够快速响应。 优化数据库连接池配置可以避免启动时因等待数据库连接而造成的延迟[^3]。 #### 7. 使用缓存机制 对于一些需要频繁访问的资源(如配置文件、静态数据等),可以使用缓存机制来减少重复加载的时间。 - 使用 `@Cacheable` 注解来缓存方法调用的结果。 - 配置合适的缓存策略,确保缓存数据的有效性和一致性。 使用缓存机制可以减少重复加载资源的时间,从而加快应用的启动速度[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值