针对 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 个优化点的实战优先级
在不做大规模重构、不升级大版本的前提下,可以按下面的优先级落地执行:
- 禁用不必要的自动配置(收益最高,改动小,立竿见影);
- 缩小包扫描范围(控制扫描粒度,防止历史垃圾代码拖累启动);
- 延迟初始化非核心 Bean(让启动阶段更"瘦身",核心功能先跑起来);
- JVM 参数优化(中长期收益可观,需结合环境调优);
- 移除冗余依赖与插件(治理项目"技术债",顺带给后续升级打基础)。
配合这 5 个优化点,实践中老项目启动时间从 30 秒+ 压缩到 10~15 秒 是完全可以做到的。
7.2 拓展建议
-
逐步升级到 Spring Boot 2.x
- 新版本在自动配置、启动性能、监控指标等方面都有明显优化;
- 可考虑先引入兼容性分支,小步升级到 2.1/2.3 等长期维护版本。
-
引入 Spring Boot DevTools 提升开发体验
- 支持自动重启、静态资源热加载;
- 与 IDE 配合使用,可以极大缩短"改代码→看效果"的闭环时间。
⚠️ 建议:老项目的性能治理,要把"启动时间优化"纳入整体技术债清单
- 与依赖升级、JDK 升级、日志体系重构等一起规划;
- 避免每次都是临时救火,而是有节奏地演进。
技术交流
欢迎在评论区留言讨论,一起把"又慢又旧"的项目,稳稳地优化到"又稳又快"。