Spring Boot 2.7升级3.2完整实战:Java 17与Jakarta迁移指南
2026/9/1 5:37:19 网站建设 项目流程

如果你最近也在刷开发者社区,大概率看到过“Tibo 发文引热议”的消息。关于那篇文章里具体争论什么,这里不展开聊,但背后有一个问题很值得认真思考:一个项目到底什么时候才算“是时候了”进行技术升级?这两年问得最多的就是 Spring Boot 2.7 要不要升到 Spring Boot 3,升了会不会踩坑,不升又会不会被依赖和安全问题拖住。与其一直停留在“要不要升”的纠结里,不如把整套迁移思路完整拆开,从核心概念、环境准备、代码改动到排错清单,一次性理清楚。

本文会以一个常见的 Spring Boot 2.7 项目为例,按照真实迁移顺序演示如何升级到 Spring Boot 3.2,并覆盖 Java 17、Jakarta EE、Spring Security 6、配置属性变化、第三方依赖兼容性等关键点。无论你是后端开发、项目负责人,还是准备在简历里加上“Spring Boot 3 迁移经验”的学习者,这篇文章都能给你一套可直接落地的参考方案。

1. 背景与核心概念

1.1 Spring Boot 3.0 解决了什么问题

Spring Boot 3.0 是一个跨度非常大的版本,底层基于 Spring Framework 6.0,最低要求 JDK 17。它带来了一大波新能力:

  • 全面拥抱 Jakarta EE,将原有的javax.*命名空间升级为jakarta.*
  • 支持 Native Image,可以结合 GraalVM 将应用编译成原生可执行文件,启动速度和内存占用都有明显优化。
  • 更好的可观测性支持,引入 Micrometer Tracing。
  • 基础依赖同步升级,比如 Tomcat 10、Hibernate 6、Spring Security 6。

很多团队一直停留在 Spring Boot 2.3、2.5 或 2.7,主要是因为升级风险大、涉及面广。但 Spring Boot 2.7 已经是 2.x 的最后一个功能分支,社区支持和维护会逐渐收紧。对于长期维护的项目来说,“是时候了”并不是头脑发热,而是因为老版本会慢慢变成安全短板和兼容性瓶颈。

1.2 升级 Spring Boot 3 的核心收益

从实际项目角度看,升级带来的收益主要有三类:

收益类别说明
安全与维护Spring Boot 2.x 停止 OSS 支持后,漏洞修复和版本更新频率会下降,升级是降低风险的手段
性能与体验新版本启动速度更快,对容器化部署更友好,内存占用有优化空间
新特性使用想用虚拟线程、Native Image、更灵活的可观测性,必须以 Spring Boot 3 为基础

不过收益不是白来的。升级意味着构建文件、代码包名、安全配置、第三方依赖都需要调整。如果项目里使用了老旧的 MyBatis、ShardingSphere、Druid 等组件,还需要逐一确认兼容版本。

1.3 升级前需要想清楚的问题

在开始动手之前,建议先问自己四个问题:

  1. 线上系统有多少个微服务?全部升级还是一部分先试点?
  2. 项目中有没有直接依赖javax.*的老代码或自定义 starter?
  3. 第三方中间件有没有 Jakarta 兼容版本?
  4. 核心业务有没有完善的自动化测试可以兜底?

这四个问题决定了升级策略是“激进式全量升级”还是“灰度式分批升级”。大多数情况下,更推荐后者。

2. 环境准备与版本说明

2.1 版本要求

在开始升级之前,先确认本机环境是否满足 Spring Boot 3 的基本要求:

组件要求说明
JDK17 或更高推荐 17 LTS,也可以使用 21 LTS
Maven3.6.3+需要支持新版插件
Gradle7.5+如果使用 Gradle,版本不能太低
目标 Spring Boot3.x本文示例以 3.2.x 为例

需要注意的是,不同 Spring Boot 3.x 小版本对 JDK 的支持范围有差异。请以官方文档对应版本为准。实际项目中不要盲目追最新,优先选择已经发布一段时间、社区反馈比较稳定的版本。

2.2 示例项目结构

为了演示方便,我准备了一个简单的用户管理项目,技术栈为:

  • Spring Boot 2.7.18
  • Spring Web
  • Spring Data JPA
  • Spring Security
  • H2 内存数据库

项目结构如下:

upgrade-demo ├── pom.xml └── src ├── main │ ├── java │ │ └── com │ │ └── example │ │ └── demo │ │ ├── DemoApplication.java │ │ ├── config │ │ │ └── SecurityConfig.java │ │ ├── controller │ │ │ └── UserController.java │ │ ├── entity │ │ │ └── User.java │ │ ├── repository │ │ │ └── UserRepository.java │ │ └── service │ │ └── UserService.java │ └── resources │ └── application.yml └── test └── java └── com └── example └── demo └── DemoApplicationTests.java

生产环境中的数据源、Redis、消息队列等配置会比这里复杂,但迁移思路是通用的。

2.3 升级前的备份与分支管理

升级属于高风险变更,千万不要直接在主干分支上随意修改。建议从当前主干拉出一个独立分支:

git checkout -b feature/springboot3-upgrade

同时保留当前可运行版本的标签,方便回退:

git tag release-springboot-2.7.18

如果项目通过私有 Nexus 管理依赖,升级前最好确认目标版本和第三方兼容版本已经同步到私服。

3. 核心变化点拆解

3.1 Java 17 与 Jakarta EE:从 javax 到 jakarta

Spring Boot 3 最重要的底层变化,是从 Java EE 切换到了 Jakarta EE 9。最直观的改动就是项目里大量import javax.*变成了import jakarta.*

常见的包名变化如下:

旧包名新包名
javax.persistence.*jakarta.persistence.*
javax.servlet.*jakarta.servlet.*
javax.validation.*jakarta.validation.*
javax.annotation.*jakarta.annotation.*

如果项目里用了 Servlet 过滤器、自定义校验注解、JPA 实体,都需要全局搜索替换。建议先用 IDE 的全局搜索确认影响范围:

搜索内容:javax.persistence 搜索范围:所有文件

然后再手动替换。不要直接全局无脑替换,有些第三方依赖内部仍可能引用旧包名,需要同步升级依赖版本。

3.2 Spring Security 6:告别 WebSecurityConfigurerAdapter

Spring Security 6 是 Spring Boot 3 里让很多人头疼的一部分。旧版写法:

@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/public/**").permitAll() .anyRequest().authenticated(); } }

升级后WebSecurityConfigurerAdapter已经被移除。新的写法是基于SecurityFilterChain的组件式配置:

@Configuration @EnableWebSecurity public class SecurityConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) .authorizeHttpRequests(auth -> auth .requestMatchers("/public/**").permitAll() .anyRequest().authenticated()) .httpBasic(Customizer.withDefaults()); return http.build(); } }

这里的关键变化:

  • authorizeRequests()改成了authorizeHttpRequests()
  • antMatchers()改成了requestMatchers()
  • 使用 Lambda DSL 风格配置。
  • 如果不使用 CSRF 防护需要显式关闭。

这样的设计更符合 Spring 的推荐做法,也避免了对全局配置类的继承耦合。

3.3 配置属性变化与自动检测

Spring Boot 3 对很多application.propertiesapplication.yml配置项做了梳理。有些属性被重命名,有些被删除,有些只是迁移到了更清晰的命名空间。

比较常见的像:

# 旧写法 management.metrics.export.prometheus.enabled=true # 新版本部分属性发生变化,需要根据版本确认 management.prometheus.metrics.export.enabled=true

为了避免手工排查遗漏,Spring Boot 官方提供了一个迁移辅助依赖:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-properties-migrator</artifactId> <scope>runtime</scope> </dependency>

添加这个依赖后,应用启动时或配置解析时,会打印属性迁移提示,帮助你发现已失效的配置项。升级完成后,记得把这个依赖从pom.xml中移除。

3.4 第三方依赖兼容性

很多项目升级卡住,不是 Spring Boot 本身的问题,而是第三方 starter 没有跟上 Jakarta 命名空间。例如:

  • MyBatis 的 starter 需要升级到支持 Spring Boot 3 的版本。
  • Druid 连接池需要确认是否支持jakarta.*
  • ShardingSphere 需要选择兼容 Spring Boot 3 的分支。
  • 老版本 Flyway 需要升级到支持 Spring Boot 3 的版本。

这里无法列出固定版本号,因为版本变化太快,不同项目实际使用的版本也不同。最稳妥的方法是去对应开源项目的官方兼容矩阵中确认,优先选择明确标注支持 Spring Boot 3 / Jakarta 的版本。

4. 完整实战案例:从 Spring Boot 2.7 迁移到 3.2

下面开始走一遍完整的迁移过程。

4.1 修改构建文件

首先修改pom.xml中的 Spring Boot 版本:

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.1</version> <relativePath/> </parent>

同时把 Java 版本调整为 17:

<properties> <java.version>17</java.version> </properties>

如果你使用 Gradle,对应修改:

plugins { id 'org.springframework.boot' version '3.2.1' id 'io.spring.dependency-management' version '1.1.4' id 'java' } java { toolchain { languageVersion = JavaLanguageVersion.of(17) } }

注意,Spring Boot 3 要求父 POM 或 BOM 的版本不能低于 3.0.0。如果项目里是通过自定义父 POM 引入依赖管理,需要确认spring-boot-dependencies的版本也一起更新。

4.2 修改启动类和实体类

启动类通常不需要大改:

// 文件路径:src/main/java/com/example/demo/DemoApplication.java package com.example.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }

实体类中需要替换 JPA 依赖的包名:

// 文件路径:src/main/java/com/example/demo/entity/User.java package com.example.demo.entity; import jakarta.persistence.*; @Entity @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; public User() { } public User(String name) { this.name = name; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }

如果你的项目里还使用了javax.validation.constraints.*,也要统一替换为jakarta.validation.constraints.*

4.3 编写 Controller、Service、Repository

Controller、Service、Repository 这三层在大多数情况下可以保持原有代码结构。

UserController:

// 文件路径:src/main/java/com/example/demo/controller/UserController.java package com.example.demo.controller; import com.example.demo.entity.User; import com.example.demo.service.UserService; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/api/users") public class UserController { private final UserService userService; public UserController(UserService userService) { this.userService = userService; } @GetMapping public List<User> list() { return userService.list(); } @PostMapping public User create(@RequestBody User user) { return userService.save(user); } }

UserService:

// 文件路径:src/main/java/com/example/demo/service/UserService.java package com.example.demo.service; import com.example.demo.entity.User; import com.example.demo.repository.UserRepository; import org.springframework.stereotype.Service; import java.util.List; @Service public class UserService { private final UserRepository userRepository; public UserService(UserRepository userRepository) { this.userRepository = userRepository; } public List<User> list() { return userRepository.findAll(); } public User save(User user) { return userRepository.save(user); } }

UserRepository:

// 文件路径:src/main/java/com/example/demo/repository/UserRepository.java package com.example.demo.repository; import com.example.demo.entity.User; import org.springframework.data.jpa.repository.JpaRepository; public interface UserRepository extends JpaRepository<User, Long> { }

从代码层面可以看到,升级的核心并不在业务代码,而在于依赖版本和框架 API 的变化。

4.4 重写 Spring Security 配置

把原来继承WebSecurityConfigurerAdapter的配置类删掉,替换为基于SecurityFilterChain的写法:

// 文件路径:src/main/java/com/example/demo/config/SecurityConfig.java package com.example.demo.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.web.SecurityFilterChain; @Configuration @EnableWebSecurity public class SecurityConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) .authorizeHttpRequests(auth -> auth .requestMatchers("/public/**").permitAll() .requestMatchers("/api/users/**").authenticated() .anyRequest().permitAll()) .httpBasic(Customizer.withDefaults()); return http.build(); } }

这里用requestMatchers替代了antMatchers,同时把 URL 匹配规则集中在一个authorizeHttpRequests中,整体阅读起来更清晰。

4.5 修改 application.yml

接下来调整配置文件。示例配置如下:

# 文件路径:src/main/resources/application.yml server: port: 8080 servlet: context-path: /demo spring: application: name: upgrade-demo datasource: url: jdbc:h2:mem:testdb driver-class-name: org.h2.Driver username: sa password: jpa: hibernate: ddl-auto: create-drop show-sql: true management: endpoints: web: exposure: include: health,info,metrics

如果项目原先配置了spring.datasource.driver-class-namecom.mysql.jdbc.Driver,升级时注意驱动类名可能已经变化。比如 MySQL 官方新版驱动推荐使用:

spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver

如果你的项目是纯 H2 示例,则不需要额外处理。

4.6 处理第三方依赖

在示例项目中,假设没有复杂第三方依赖,因此升级后依赖比较简单。实际项目中,第三方依赖的处理是最容易出现意外的地方。

比如老版本的 MyBatis 启动器:

<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.3.1</version> </dependency>

这类旧版本可能无法直接兼容 Spring Boot 3。需要去 MyBatis 官方社区查看支持 Spring Boot 3 的 starter 版本,升级后再重新编译。

4.7 运行与验证

完成上述修改后,可以先执行编译:

mvn clean compile

如果编译通过,再执行测试:

mvn test

最后运行应用:

mvn spring-boot:run

启动成功后,访问接口:

  • 查询用户列表:GET http://localhost:8080/demo/api/users
  • 健康检查:GET http://localhost:8080/demo/actuator/health

由于接口上有安全配置,未带认证信息访问/api/users会返回 401。因为 H2 内存数据库会在应用启动时初始化表结构,所以可以先调用POST接口新增用户,再调用GET接口查看数据。

如果你看到类似下面的输出,说明应用已经正常启动:

Tomcat started on port 8080 (http) with context path '/demo' Started DemoApplication in 2.5 seconds

4.8 使用迁移辅助工具

除了手动修改,Spring 官方还提供了一些迁移辅助工具,例如 Spring Boot Migrator。这类工具可以扫描老项目,并尝试自动完成部分迁移。不过自动化工具处理不了所有场景,尤其是复杂的安全配置和第三方依赖。建议把工具当作辅助,最终还是要靠人工 review 和测试兜底。

5. 常见问题与排查思路

升级过程中最容易遇到下面这些报错,这里整理成一份排错清单。

问题现象常见原因解决思路
ClassNotFoundException: javax.servlet.Filter依赖引用了旧javax全局搜索javax.servlet,改为jakarta.servlet,并升级相关依赖
NoClassDefFoundError: WebSecurityConfigurerAdapterSpring Security 6 已移除旧适配器改为使用SecurityFilterChainBean
应用启动失败,提示PathPattern匹配问题Spring Boot 3 默认使用PathPatternParser检查 Controller 和 SecurityConfig 中 URL 通配符,推荐使用/**
数据库方言错误Hibernate 6 方言配置变化删除手动配置的 dialect,让 Hibernate 自动识别
MyBatis 相关 Bean 无法注入MyBatis starter 版本太旧升级到支持 Spring Boot 3 的版本
配置属性提示Unknown propertySpring Boot 3 清理了老属性添加spring-boot-properties-migrator定位,按提示迁移
java.lang.reflect.InaccessibleObjectExceptionJDK 17 模块限制优先升级依赖,不要一上来就加--add-opens
启动耗时变长或部分 Bean 加载失败依赖注入或自动配置类被误替换查看启动日志中的BeanCreationException堆栈,定位到具体配置

在实际项目里,最常见的情况不是某个大坑,而是各种小问题叠加在一起。所以升级过程中要保持耐心,建议每改完一部分就编译一次,不要等到最后一起解决所有报错。

6. 最佳实践与工程建议

6.1 升级前先做依赖清单盘点

花半天时间把项目里所有依赖梳理出来,重点标记三类:

  • 官方维护的 Spring Boot starter。
  • 第三方 starter 和 SDK。
  • 项目内部公共模块。

这样升级时就能快速判断哪些依赖需要同步升级,哪些依赖可能没有兼容版本。

6.2 使用灰度发布,不要一把梭

线上系统升级不建议一次全部替换。可以把 1 到 2 个非核心服务先升级,观察运行稳定性和性能指标。确认没问题后,再逐步扩大范围。

如果服务通过 Nacos、Consul 等服务注册中心管理,可以结合流量权重做灰度。例如先切 10% 流量到新版本,观察错误率、耗时、GC 情况。

6.3 测试要提前准备

升级前先确认测试覆盖率。重点测试:

  • 登录认证和权限控制链路。
  • 用户核心读写链路。
  • 定时任务和消息消费链路。
  • 对外提供的 OpenAPI 接口。

测试不一定要多,但核心链路必须能自动化回归。否则升级后出现问题,很难判断是代码问题还是配置问题。

6.4 理解并利用迁移辅助依赖

在升级过程中,可以在pom.xml临时加入:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-properties-migrator</artifactId> <scope>runtime</scope> </dependency>

启动时它会提示哪些属性已经变化或是无效。定位并修复所有提示后,移除这个依赖,避免把调试信息带到生产环境。

6.5 关注安全与权限变更

Spring Security 6 的默认行为比旧版本更严格。尤其在 CSRF、跨域配置、请求匹配规则上,不要为了通过测试而随意关闭安全限制。生产环境使用最小权限原则,对外暴露的接口尽量显式配置白名单,不要使用anyRequest().permitAll()覆盖所有路径。

6.6 保留回滚方案

升级部署前,除了常规备份,还需要保留旧版本的可运行产物。建议在发布流程中加上回滚按钮或快速回退脚本。回滚不仅依赖代码,还依赖数据库变更。如果升级过程中有增量 SQL 脚本,要评估向下兼容性,避免回滚后数据库结构和旧代码不匹配。

7. 总结与学习路线

Spring Boot 3 迁移并不可怕,真正需要重视的是变化点梳理和回归验证。本文通过一个完整的示例项目,演示了从 Spring Boot 2.7 升级到 3.2 的核心步骤,覆盖了 Java 17、Jakarta EE、Spring Security 6、配置属性迁移和第三方依赖兼容性等关键内容。

完成基础迁移后,下一步可以继续学习:

  • Spring Security 6 的授权语义和过滤器链机制。
  • Spring Boot 3 对 GraalVM Native Image 的支持。
  • JDK 21 虚拟线程在 Spring Boot 3.2 中的实践。
  • Micrometer Tracing 与可观测性体系搭建。

对于实际项目,建议优先关注安全配置、中间件兼容性和回滚预案,不要只盯着版本号。升级本身不是目标,让项目更稳定、更可持续演进才是“是时候了”的真正含义。

如果这篇文章对你有帮助,可以先收藏备用。动手迁移时如果遇到新的问题,欢迎在评论区一起讨论。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询