终于解决了!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-mailspring-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 升级、日志体系重构等一起规划;
  • 避免每次都是临时救火,而是有节奏地演进。

技术交流

欢迎在评论区留言讨论,一起把"又慢又旧"的项目,稳稳地优化到"又稳又快"。

相关推荐
zgl_200537795 分钟前
ZGLanguage 解析SQL数据血缘 之 标识提取SQL语句中的目标表
java·大数据·数据库·数据仓库·hadoop·sql·源代码管理
liwulin05066 分钟前
【JAVA】创建一个不需要依赖的websocket服务器接收音频文件
java·服务器·websocket
钦拆大仁13 分钟前
统一数据返回格式和统一异常处理
java
czlczl2002092525 分钟前
OAuth 2.0 解析:后端开发者视角的原理与流程讲解
java·spring boot·后端
颜淡慕潇33 分钟前
Spring Boot 3.3.x、3.4.x、3.5.x 深度对比与演进分析
java·后端·架构
g***557535 分钟前
Java高级开发进阶教程之系列
java·开发语言
阿达King哥1 小时前
在Windows11下编译openjdk 21
java·jvm
shark-chili1 小时前
从操作系统底层浅谈程序栈的高效性
java
不知疲倦的仄仄1 小时前
第二天:深入理解 Selector:单线程高效管理多个 Channel
java·nio
期待のcode2 小时前
Java虚拟机栈
java·开发语言·jvm