Spring Boot 3.2升级实战:从2.7平稳迁移的10个关键步骤
前言:为什么现在必须升级到Spring Boot 3.2?
如果你还在使用Spring Boot 2.7.x,你可能面临这些问题:
-
🚫 2024年8月后Spring Boot 2.7将停止维护(包括安全更新)
-
🚫 无法使用JDK 21的虚拟线程等新特性
-
🚫 错过Spring Framework 6的性能提升(响应速度提升40%)
-
🚫 无法集成最新AI、云原生技术栈
但升级过程确实充满挑战:包名从javax到jakarta、配置项大规模变更、第三方依赖兼容性问题...
本文将带你用最小的代价完成从2.7到3.2的平稳升级,每个步骤都包含可执行的代码和验证方法。
升级前准备:风险评估与检查清单
1.1 项目健康度检查
在开始升级前,先运行以下检查:
# 1. 检查当前依赖健康状况
mvn dependency:tree | grep -E "(javax|log4j|logback|spring-security)" > deps-audit.txt
# 2. 运行完整测试套件
mvn clean test -DskipTests=false
# 3. 生成API变更报告
mvn versions:display-plugin-updates
mvn versions:display-dependency-updates
1.2 必须完成的准备工作
✅ 备份完整代码:确保有可回退的版本
git checkout -b spring-boot-3-upgrade
git add .
git commit -m "Backup before Spring Boot 3.2 upgrade"
✅ 记录关键配置:特别关注以下文件:
-
application.properties/application.yml -
pom.xml中的依赖版本 -
自定义的
@Configuration类 -
过滤器、拦截器配置
✅ 建立性能基线:
# 记录当前应用启动时间
time mvn spring-boot:run
# 记录关键API响应时间(使用JMeter或简单脚本)
第1步:JDK版本升级(从JDK 8/11到JDK 17+)
1.1 修改pom.xml中的JDK配置
<!-- 在pom.xml的properties部分修改 -->
<properties>
<!-- 从JDK 8/11升级到JDK 17 -->
<java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<!-- 可选:启用预览特性(如需使用虚拟线程) -->
<maven.compiler.compilerArgs>
<arg>--enable-preview</arg>
</maven.compiler.compilerArgs>
</properties>
1.2 处理JDK 17的模块系统问题
如果你的项目使用了反射或动态代理,需要在module-info.java中添加:
module your.application {
requires spring.core;
requires spring.context;
requires spring.web;
// 允许反射访问
opens com.yourpackage to spring.core, spring.beans, spring.context;
// 如果使用Jackson
opens com.yourpackage.model to com.fasterxml.jackson.databind;
}
或者,在启动参数中添加(临时方案):
-Dspring.native.remove-yaml-support=false \
--add-opens java.base/java.lang=ALL-UNNAMED \
--add-opens java.base/java.util=ALL-UNNAMED
第2步:Spring Boot父POM版本升级
2.1 修改父依赖版本
<!-- 从2.7.x升级到3.2.4 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<!-- 从原来的2.7.x改为 -->
<version>3.2.4</version>
<relativePath/>
</parent>
<!-- 或者使用dependencyManagement方式 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.2.4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2.2 处理废弃的依赖
Spring Boot 3.2移除了以下starter,需要手动添加:
<!-- 如果使用了以下starter,需要单独引入 -->
<dependencies>
<!-- 1. spring-boot-starter-log4j2 (如果使用Log4j2) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
<exclusions>
<exclusion>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<!-- 2. 如果使用了spring-boot-starter-tomcat(现在内嵌在web中) -->
<!-- 无需单独声明 -->
</dependencies>
第3步:javax到jakarta的包名迁移
3.1 使用IDE的全局替换功能
在IntelliJ IDEA或Eclipse中执行全局替换:
-
查找:
javax.persistence→ 替换为:jakarta.persistence -
查找:
javax.servlet→ 替换为:jakarta.servlet -
查找:
javax.validation→ 替换为:jakarta.validation -
查找:
javax.annotation→ 替换为:jakarta.annotation
3.2 关键变更点检查清单
// 1. JPA实体类
@Entity
@Table(name = "users")
public class User {
// @Id, @GeneratedValue 等注解现在来自jakarta包
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}
// 2. Controller中的校验注解
@RestController
public class UserController {
@PostMapping("/users")
public ResponseEntity<User> createUser(
@Valid @RequestBody UserDto userDto) { // @Valid现在来自jakarta
// ...
}
}
// 3. 过滤器配置
@Component
public class AuthFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(
HttpServletRequest request, // jakarta.servlet.http.HttpServletRequest
HttpServletResponse response,
FilterChain filterChain) {
// ...
}
}
3.3 自动化迁移脚本(可选)
对于大型项目,可以使用OpenRewrite自动迁移:
<!-- 在pom.xml中添加 -->
<build>
<plugins>
<plugin>
<groupId>org.openrewrite.maven</groupId>
<artifactId>rewrite-maven-plugin</artifactId>
<version>5.40.0</version>
<configuration>
<activeRecipes>
<recipe>org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_2</recipe>
<recipe>org.openrewrite.java.migrate.jakarta.JavaxToJakarta</recipe>
</activeRecipes>
</configuration>
<dependencies>
<dependency>
<groupId>org.openrewrite.recipe</groupId>
<artifactId>rewrite-spring</artifactId>
<version>5.7.0</version>
</dependency>
<dependency>
<groupId>org.openrewrite.recipe</groupId>
<artifactId>rewrite-migrate-java</artifactId>
<version>2.13.0</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
运行迁移:
mvn rewrite:run
第4步:Spring Security 5.x到6.x的迁移
4.1 配置类重大变更
// Spring Security 5.x的配置(需要修改)
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/public/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.and()
.httpBasic();
}
}
// Spring Security 6.x的配置(新写法)
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/public/**").permitAll()
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.permitAll()
)
.httpBasic(Customizer.withDefaults());
return http.build();
}
}
4.2 关键API变更
| Spring Security 5.x | Spring Security 6.x | 迁移说明 |
|---|---|---|
antMatchers() |
requestMatchers() |
方法名变更 |
mvcMatchers() |
requestMatchers() |
统一使用新API |
.and() |
Lambda DSL | 不再需要链式调用 |
WebSecurityConfigurerAdapter |
SecurityFilterChain Bean |
类已废弃 |
4.3 密码编码器变更
// 旧方式(Spring Security 5.x)
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// 新方式(Spring Security 6.x)- 推荐使用DelegatingPasswordEncoder
@Bean
public PasswordEncoder passwordEncoder() {
String encodingId = "bcrypt";
Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put(encodingId, new BCryptPasswordEncoder());
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
return new DelegatingPasswordEncoder(encodingId, encoders);
}
第5步:数据库连接与JPA变更
5.1 数据源配置变更
# Spring Boot 2.7.x的配置(旧)
spring:
datasource:
url: jdbc:mysql://localhost:3306/db
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
connection-timeout: 30000
maximum-pool-size: 10
# Spring Boot 3.2.x的配置(新)
spring:
datasource:
url: jdbc:mysql://localhost:3306/db
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
connection-timeout: 30000
maximum-pool-size: 10
# 新增:SQL初始化模式
initialization-mode: always # 改为:spring.sql.init.mode=always
5.2 JPA配置变更
# 重要的配置变更
spring:
jpa:
# 旧配置(2.7.x)
# hibernate:
# ddl-auto: update
# naming:
# physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
# 新配置(3.2.x)
hibernate:
ddl-auto: update
# 命名策略配置方式变更
naming:
physical-strategy: org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy
# 新增:延迟加载配置
properties:
hibernate:
enable_lazy_load_no_trans: true
"[hibernate.format_sql]": true
# SQL初始化配置(从datasource中分离)
sql:
init:
mode: always
platform: mysql
5.3 数据库驱动版本升级
必须更新数据库驱动版本:
<!-- MySQL驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.2.0</version> <!-- 必须从8.0.x升级 -->
<scope>runtime</scope>
</dependency>
<!-- PostgreSQL驱动 -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.1</version> <!-- 必须从42.5.x升级 -->
<scope>runtime</scope>
</dependency>
第6步:日志框架配置迁移
6.1 Logback配置变更
<!-- logback-spring.xml 重要变更 -->
<configuration>
<!-- 1. 移除过时的属性 -->
<!-- 旧:<property name="LOG_PATH" value="./logs" /> -->
<!-- 新: -->
<property name="LOG_PATH" value="${LOG_PATH:-./logs}" />
<!-- 2. RollingFileAppender配置变更 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/application.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/application.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!-- 最大文件大小配置变更 -->
<maxFileSize>100MB</maxFileSize> <!-- 单位必须明确 -->
<maxHistory>30</maxHistory>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 3. 异步日志配置 -->
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<queueSize>512</queueSize>
<discardingThreshold>0</discardingThreshold>
<appender-ref ref="FILE" />
<!-- 新增:避免日志丢失的配置 -->
<neverBlock>true</neverBlock>
</appender>
</configuration>
6.2 Log4j2配置(如果使用)
<!-- log4j2-spring.xml 配置示例 -->
<Configuration status="WARN" monitorInterval="30">
<Properties>
<Property name="LOG_PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n</Property>
<Property name="LOG_PATH">./logs</Property>
</Properties>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="${LOG_PATTERN}"/>
</Console>
<RollingFile name="File" fileName="${LOG_PATH}/app.log"
filePattern="${LOG_PATH}/app-%d{yyyy-MM-dd}-%i.log">
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
<DefaultRolloverStrategy max="30"/>
</RollingFile>
</Appenders>
<Loggers>
<Logger name="com.yourpackage" level="debug" additivity="false">
<AppenderRef ref="File"/>
</Logger>
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
第7步:Web配置与Servlet API变更
7.1 WebMvcConfigurer变更
// Spring Boot 2.7.x(旧)
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://localhost:3000")
.allowedMethods("GET", "POST");
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor());
}
}
// Spring Boot 3.2.x(新)
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://localhost:3000")
.allowedMethods("GET", "POST")
.allowCredentials(true); // 新增:必须显式设置
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/static/**", "/error");
}
}
7.2 文件上传配置变更
# 文件上传配置变更
spring:
servlet:
multipart:
# 旧配置(2.7.x)
# max-file-size: 10MB
# max-request-size: 100MB
# 新配置(3.2.x)
max-file-size: 10MB
max-request-size: 100MB
# 新增:内存阈值配置
file-size-threshold: 0B
# 新增:清理临时文件
location: ${java.io.tmpdir}
# 新增:HTTP配置分离
web:
resources:
cache:
period: 3600
第8步:Actuator监控端点变更
8.1 端点配置变更
# Spring Boot 2.7.x Actuator配置
management:
endpoints:
web:
exposure:
include: health,info,metrics
endpoint:
health:
show-details: always
# Spring Boot 3.2.x Actuator配置
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus # 新增prometheus
base-path: /actuator # 现在可以自定义
endpoint:
health:
show-details: always
# 新增:健康检查组配置
group:
custom:
include: diskSpace,ping,db
# 新增:度量的新配置
metrics:
export:
prometheus:
enabled: true
step: 1m
8.2 自定义健康检查
// Spring Boot 3.2.x的健康检查写法
@Component
public class CustomHealthIndicator implements HealthIndicator {
private final DataSource dataSource;
public CustomHealthIndicator(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public Health health() {
// 检查数据库连接
try (Connection conn = dataSource.getConnection()) {
if (conn.isValid(1000)) {
return Health.up()
.withDetail("database", "available")
.withDetail("connection_time", "fast")
.build();
}
} catch (SQLException e) {
return Health.down(e)
.withDetail("database", "unavailable")
.withDetail("error", e.getMessage())
.build();
}
return Health.unknown().build();
}
}
第9步:第三方依赖兼容性处理
9.1 常见依赖升级指南
<!-- 1. MyBatis升级 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version> <!-- 从2.3.x升级 -->
</dependency>
<!-- 2. 如果使用Druid连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-3-starter</artifactId>
<version>1.2.20</version> <!-- 注意:必须是3.x的starter -->
</dependency>
<!-- 3. 如果使用Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<!-- 注意:需要显式添加commons-pool2 -->
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.12.0</version>
</dependency>
<!-- 4. 如果使用Swagger/SpringDoc -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.3.0</version> <!-- 替代springfox -->
</dependency>
9.2 必须检查的依赖
运行以下命令检查不兼容的依赖:
# 查找所有javax依赖
mvn dependency:tree | grep javax
# 查找所有可能不兼容的依赖
mvn dependency:tree | grep -E "(servlet|persistence|validation|annotation)" | grep -v jakarta
# 使用Maven Enforcer插件检查
mvn enforcer:enforce -Drules=dependencyConvergence
第10步:测试与验证
10.1 分阶段验证策略
第一阶段:编译验证
# 清理并编译
mvn clean compile
# 检查是否有编译错误
if [ $? -eq 0 ]; then
echo "✅ 编译通过"
else
echo "❌ 编译失败,请检查错误信息"
exit 1
fi
第二阶段:单元测试
# 运行所有单元测试
mvn test -DskipTests=false
# 检查测试通过率
TEST_RESULT=$(mvn test -DskipTests=false 2>&1 | grep "Tests run:")
echo "测试结果:$TEST_RESULT"
第三阶段:集成测试
# 使用Testcontainers运行集成测试
mvn verify -DskipITs=false
# 或者手动启动应用测试
mvn spring-boot:run &
APP_PID=$!
# 等待应用启动
sleep 30
# 测试关键端点
curl -f http://localhost:8080/actuator/health || echo "健康检查失败"
curl -f http://localhost:8080/api/test || echo "API测试失败"
# 停止应用
kill $APP_PID
10.2 性能对比测试
创建性能测试脚本:
#!/bin/bash
# 性能对比测试脚本
echo "=== Spring Boot 2.7.x 性能测试 ==="
# 启动旧版本应用
# 运行压测...
echo "=== Spring Boot 3.2.x 性能测试 ==="
# 启动新版本应用
# 运行同样压测...
# 比较结果
echo "性能提升:"
echo "- 启动时间:减少约30%"
echo "- 内存占用:减少约20%"
echo "- 吞吐量:提升约15%"
10.3 回滚计划
如果升级过程中遇到问题,需要准备回滚方案:
# 1. 代码回滚
git reset --hard HEAD~1
# 2. 数据库回滚(如果执行了迁移脚本)
# 备份恢复...
# 3. 配置文件恢复
cp application-backup.yml application.yml
# 4. 依赖恢复
mvn clean install -U
升级后优化建议
1. 启用新特性
// 1. 启用虚拟线程(需要JDK 21+)
@Bean
public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() {
return protocolHandler -> {
protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
};
}
// 2. 使用新的RestClient(替代RestTemplate)
@Bean
public RestClient restClient() {
return RestClient.builder()
.baseUrl("https://api.example.com")
.defaultHeader("User-Agent", "SpringBootApp")
.build();
}
// 3. 响应式编程(如果适用)
@Bean
public RouterFunction<ServerResponse> routes() {
return RouterFunctions.route()
.GET("/users", request -> ServerResponse.ok().bodyValue(userService.findAll()))
.build();
}
2. 监控与告警配置
# 新增:Micrometer监控配置
management:
metrics:
export:
prometheus:
enabled: true
datadog:
enabled: false
newrelic:
enabled: false
distribution:
percentiles-histogram:
"[http.server.requests]": true
tags:
application: ${spring.application.name}
environment: ${ENV:local}
tracing:
sampling:
probability: 1.0
常见问题与解决方案
Q1:启动时出现ClassNotFoundException: javax.servlet.Filter
原因:没有正确迁移到jakarta
解决:
-
检查所有import语句
-
确保依赖中没有旧的servlet-api
-
运行
mvn dependency:tree | grep servlet检查
Q2:Hibernate无法启动,报Table not found
原因:DDL自动生成策略变化
解决:
spring:
jpa:
hibernate:
ddl-auto: validate # 先使用validate模式
properties:
hibernate:
"[hibernate.hbm2ddl.auto]": validate
Q3:Spring Security配置不生效
原因:SecurityFilterChain配置错误
解决:
@Bean
@Order(1) // 确保顺序正确
public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/api/**") // 明确指定匹配路径
.authorizeHttpRequests(auth -> auth
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults());
return http.build();
}
Q4:性能下降或内存泄漏
排查步骤:
-
使用
jcmd <pid> VM.native_memory分析内存 -
检查GC日志:
-Xlog:gc*:file=gc.log -
使用AsyncProfiler进行性能分析
总结
从Spring Boot 2.7升级到3.2是一个系统性的工程,但收益显著。按照这10个步骤,你可以:
-
安全地完成升级,避免生产事故
-
充分利用新特性,提升应用性能
-
保持技术栈先进性,为未来升级奠定基础
最后建议:
-
在测试环境充分验证至少1周
-
逐步灰度发布到生产环境
-
建立监控告警,关注关键指标
升级成功标志:
✅ 应用启动时间减少30%以上
✅ 内存占用降低20%以上
✅ 所有测试用例通过
✅ 生产环境运行稳定24小时+
记住:一次成功的升级,不仅是为了使用新版本,更是为了构建更稳定、高效的技术基础。
升级支持:如果在升级过程中遇到问题,可以在评论区留言,我会提供针对性的解决方案。
资源推荐:
更多推荐




所有评论(0)