Spring Boot 2.x 升级到 3.x:微服务项目实战操作指南

适用对象:正在维护 Spring Boot 2.x、Spring Cloud、Spring Cloud Alibaba、Spring Security OAuth2 老项目的后端团队
示例模块:ssogatewayorder-serviceuser-service,均为通用示例命名

Spring Boot 3 不是一次普通的版本号升级。它同时带来了 JDK 17、Spring Framework 6、Jakarta EE、Spring Security 6、Spring Authorization Server、Spring Cloud 新版本线等一组连锁变化。

如果只把 pom.xml 里的版本号改大,项目通常会卡在以下位置:

  • 构建环境仍然使用 JDK 8 或 JDK 11。
  • 老的 javax.servlet.*javax.annotation.*javax.validation.* 没有迁移到 jakarta.*
  • Spring Security 仍然使用 WebSecurityConfigurerAdapterResourceServerConfigurerAdapterantMatchers()
  • 老的 spring-security-oauthTokenStoreRedisTokenStore 与 Spring Security 6 不兼容。
  • Spring Cloud、Nacos、Gateway、OpenFeign 版本没有和 Spring Boot 3.x 对齐。
  • JVM 启动参数仍然是 JDK 8 时代写法,GC 日志、CMS、PermGen 参数全部需要调整。

这篇文章按“能落地执行”的方式写:先定版本矩阵,再做构建、依赖、代码、安全、JVM、验证、上线。示例尽量贴近微服务项目常见结构,而不是绑定某个具体业务系统。

一、先确定目标版本矩阵

升级前第一件事不是改代码,而是把版本矩阵定清楚。

Spring Boot 3.x 的底层是 Spring Framework 6,最低要求是 Java 17。Spring Boot 3.5 官方文档要求 Java 17,并兼容到更高版本 JDK。Spring Boot 3.0 迁移指南也建议先升级到 Spring Boot 2.7 最新补丁,再迁移到 3.x。

一个典型 Spring Cloud 微服务项目可以参考下面的矩阵:

组件 推荐目标线 说明
JDK 17+ Spring Boot 3.x 硬性要求,建议先统一开发机、CI、镜像
Spring Boot 3.5.x 示例使用 3.5.x,实际以公司验证版本为准
Spring Cloud 2025.0.x 对应 Spring Boot 3.5.x 版本线
Spring Cloud Alibaba 2025.0.x 对应 Spring Cloud 2025.0.x / Boot 3.5.x
Nacos Client 3.x 需要与 Spring Cloud Alibaba 版本线匹配
Spring Security 6.x Boot 3 自动管理
Spring Authorization Server 1.x 替代老的 Spring Security OAuth 授权服务器
MyBatis-Spring 3.x 适配 Spring 6 / Boot 3
MyBatis-Plus 3.5.4.1+ Spring Boot 3 支持版本,分页依赖需注意

如果项目用了 Spring Cloud Alibaba,不要只看 Spring Boot 最新版本。要同时确认:

  • Spring Boot 和 Spring Cloud 是否兼容。
  • Spring Cloud Alibaba 是否支持当前 Spring Cloud 版本线。
  • Nacos Client 是否与服务端版本、配置中心、注册中心兼容。
  • 公司私服是否已经同步了目标版本依赖。

建议在升级文档或技术方案里明确写出最终矩阵。例如:

JDK: 17
Spring Boot: 3.5.x
Spring Cloud: 2025.0.x
Spring Cloud Alibaba: 2025.0.x
Nacos Client: 3.x
Spring Authorization Server: 由 Spring Boot BOM 或官方兼容版本管理

不要把“最新版本”当成唯一目标。微服务升级更关注“兼容矩阵稳定、团队能验证、线上能回滚”。

二、推荐升级路线

建议按下面的顺序推进:

新建升级分支

统一 JDK 17 构建环境

升级 Spring Boot / Cloud / Alibaba 版本矩阵

清理手写旧依赖版本

迁移 javax 到 jakarta

处理 JDK 17 API 与 JVM 参数

迁移 Spring Security 6

迁移 OAuth2 授权服务器和资源服务器

适配 Gateway / Nacos / OpenFeign / MyBatis

编译、依赖树、启动、接口回归

灰度发布和监控回滚

如果项目还在 Spring Boot 2.4、2.5、2.6,建议先升级到 Spring Boot 2.7 最新补丁,再进入 3.x。这样可以先解决一部分旧 API、JDK 17、配置项废弃问题,降低一次性跳跃的风险。

如果最终目标是 Spring Boot 4,建议先把 3.5 当成内部检查点,分两阶段推进。详见本文末尾 附录 A:Boot 4 升级路线前瞻

三、升级前盘点

微服务项目升级前要先盘点,而不是直接改 POM。

3.1 盘点模块

常见模块可以按职责分组:

模块 关注点
parent 版本管理、插件管理、仓库配置
sso 登录、发 token、客户端管理、授权服务器
gateway 路由、鉴权前置过滤器、跨域、限流、日志
common 通用 Web、安全、Redis、工具类、异常处理
order-service / user-service 资源服务器、业务接口、权限判断
job-service 定时任务、异步线程池、上下文传递

3.2 常用 Linux 扫描命令

下面的命令尽量使用 Linux 常见工具,方便在服务器、CI、容器里执行。

扫描旧 Spring Security OAuth:

find . -name "*.java" -print0 \
  | xargs -0 grep -nE "org\.springframework\.security\.oauth2\.(common|provider)|TokenStore|RedisTokenStore|ResourceServerConfigurerAdapter|AuthorizationServerConfigurerAdapter|WebSecurityConfigurerAdapter"

扫描旧 Security DSL:

find . -name "*.java" -print0 \
  | xargs -0 grep -nE "authorizeRequests\(|antMatchers\(|EnableGlobalMethodSecurity|ignoringAntMatchers"

扫描 Jakarta 迁移范围:

find . -name "*.java" -print0 \
  | xargs -0 grep -nE "javax\.(servlet|annotation|validation|persistence|transaction|websocket)\."

扫描 JDK 内部 API:

find . -name "*.java" -print0 \
  | xargs -0 grep -nE "sun\.|com\.sun\.|org\.omg\.CORBA|javafx\."

扫描旧配置项:

find . \( -name "*.yml" -o -name "*.yaml" -o -name "*.properties" \) -print0 \
  | xargs -0 grep -nE "security\.basic\.enabled|management\.security\.enabled|server\.servlet\.context-path|spring\.main\.allow-bean-definition-overriding"

扫描 POM 里的旧版本覆盖:

find . -name "pom.xml" -print0 \
  | xargs -0 grep -nE "spring-security-oauth|spring-security-jwt|spring-security-oauth2-autoconfigure|spring-security-[a-z-]+</artifactId>|tomcat-embed-|undertow-|jackson-databind|spring-cloud-starter-sleuth"

这些命令不是为了“一键替换”,而是为了找到风险点。尤其是 javax.*,不能全局替换。

四、构建环境升级到 JDK 17

4.1 统一本地、CI、镜像

先确认当前环境:

java -version
mvn -version
echo "$JAVA_HOME"

Linux 常见设置方式:

export JAVA_HOME=/usr/lib/jvm/java-17-openjdk
export PATH="$JAVA_HOME/bin:$PATH"

java -version
mvn -version

如果使用 Docker 构建,基础镜像也要同步:

FROM eclipse-temurin:17-jdk AS build
WORKDIR /workspace
COPY . .
RUN ./mvnw -DskipTests package

FROM eclipse-temurin:17-jre
WORKDIR /app
COPY --from=build /workspace/order-service/target/order-service.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]

如果使用 CI,建议在流水线里显式打印:

java -version
mvn -version

很多升级问题都是“开发机是 JDK 17,CI 还是 JDK 8”导致的。

4.2 POM 中强制 JDK 17

建议在父工程加 maven-enforcer-plugin,尽早失败:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-enforcer-plugin</artifactId>
    <version>3.5.0</version>
    <executions>
        <execution>
            <id>enforce-java-version</id>
            <goals>
                <goal>enforce</goal>
            </goals>
            <configuration>
                <rules>
                    <requireJavaVersion>
                        <version>[17,)</version>
                        <message>Spring Boot 3.x requires JDK 17+. Please run Maven with Java 17.</message>
                    </requireJavaVersion>
                </rules>
            </configuration>
        </execution>
    </executions>
</plugin>

同时设置:

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

如果项目没有使用 spring-boot-starter-parent,需要显式配置 maven-compiler-plugin

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.13.0</version>
    <configuration>
        <release>17</release>
        <encoding>UTF-8</encoding>
    </configuration>
</plugin>

<release>17</release> 通常比同时写 <source><target> 更稳,因为它会约束可用的 JDK API。

五、父 POM 和依赖版本管理

5.1 使用 Spring Boot 父工程

如果项目可以使用 spring-boot-starter-parent,推荐这样配置:

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

然后引入 Spring Cloud 和 Spring Cloud Alibaba BOM:

<properties>
    <spring-cloud.version>2025.0.x</spring-cloud.version>
    <spring-cloud-alibaba.version>2025.0.x</spring-cloud-alibaba.version>
</properties>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>${spring-cloud-alibaba.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

5.2 清理手写旧版本

升级时最忌讳“父工程升到 Boot 3,子模块还锁着 Spring 5 / Security 5 / Tomcat 9”。

常见需要删除版本号的依赖:

<!-- 删除 version,交给 Spring Boot 管理 -->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
</dependency>

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
</dependency>

建议优先使用 starter:

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

只有在确实需要排除某些传递依赖时,才显式声明 spring-security-webspring-security-configspring-security-core。即使显式声明,也不要手写旧版本。

5.3 查看依赖树

常用命令:

mvn -DskipTests dependency:tree > dependency-tree.txt

重点检查:

grep -nE "spring-core|spring-web|spring-security|tomcat-embed|undertow|jackson-databind|javax.servlet|jakarta.servlet|nacos|spring-cloud" dependency-tree.txt

如果看到下面这些组合,就要警惕:

  • Spring Boot 3.x + Spring Framework 5.x
  • Spring Boot 3.x + Spring Security 5.x
  • Spring Boot 3.x + Tomcat 9.x
  • Spring Boot 3.x + javax.servlet-api
  • Spring Cloud 2025.x + 老 Nacos Client 2.x

六、Jakarta 迁移

Spring Boot 3 基于 Spring Framework 6,全面迁移到 Jakarta EE 命名空间。

常见替换如下:

Boot 2 / Spring 5 Boot 3 / Spring 6
javax.servlet.* jakarta.servlet.*
javax.annotation.* jakarta.annotation.*
javax.validation.* jakarta.validation.*
javax.persistence.* jakarta.persistence.*
javax.transaction.* jakarta.transaction.*
javax.websocket.* jakarta.websocket.*

示例:

// before
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;

// after
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;

但不要全量替换所有 javax

以下仍属于 Java SE 标准包,不要改:

说明
javax.crypto.* JDK 加密 API
javax.net.ssl.* JDK SSL API
javax.imageio.* JDK 图片 API
javax.sound.sampled.* JDK 音频 API
javax.script.* JDK 脚本 API

推荐扫描方式:

find . -name "*.java" -print0 \
  | xargs -0 grep -nE "javax\.(servlet|annotation|validation|persistence|transaction|websocket)\."

不要用下面这种粗暴方式:

# 不建议
grep -R "javax\." -n .

它会把 Java SE 的 javax.cryptojavax.net.ssl 也扫出来,容易误改。

七、JDK 17 API 与代码调整

7.1 BASE64 内部 API

旧代码:

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

替换为:

import java.util.Base64;

public class Base64s {
    public static String encode(byte[] data) {
        return Base64.getEncoder().encodeToString(data);
    }

    public static byte[] decode(String text) {
        return Base64.getDecoder().decode(text);
    }
}

7.2 com.sun.* 私有 API

如果代码使用:

import com.sun.media.sound.WaveFileReader;
import com.sun.media.sound.WaveFileWriter;

建议改为标准 API:

import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;

AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(inputFile);
AudioSystem.write(audioInputStream, AudioFileFormat.Type.WAVE, outputFile);

7.3 CORBA / Java EE 模块移除

JDK 11 起移除了 Java EE 和 CORBA 模块。如果代码仍然引用:

import org.omg.CORBA.*;
import javax.xml.bind.*;
import javax.xml.ws.*;

需要判断是否真的使用:

  • 没有使用:删除引用。
  • JAXB 仍然使用:显式引入 Jakarta JAXB 依赖。
  • JAX-WS 仍然使用:显式引入对应实现,或评估替换为 REST / gRPC。
  • CORBA 仍然使用:建议评估架构替换。

八、JVM 参数迁移:JDK 8 到 JDK 17

JVM 参数是升级里很容易被忽略的一环。很多服务代码已经能启动,但线上因为旧 GC 参数、日志参数、容器内存识别方式不合理而出问题。

8.1 先看 JDK 8 常见启动参数

很多老服务启动脚本类似这样:

JAVA_OPTS="
  -Xms2g
  -Xmx2g
  -XX:PermSize=256m
  -XX:MaxPermSize=512m
  -XX:+UseConcMarkSweepGC
  -XX:+CMSClassUnloadingEnabled
  -XX:+PrintGCDetails
  -XX:+PrintGCDateStamps
  -Xloggc:/data/logs/gc.log
  -XX:+UseGCLogFileRotation
  -XX:NumberOfGCLogFiles=10
  -XX:GCLogFileSize=100M
  -XX:+HeapDumpOnOutOfMemoryError
  -XX:HeapDumpPath=/data/dumps
"

这套参数在 JDK 17 下需要整理。

JDK 8 参数 JDK 17 处理
-XX:PermSize 删除,PermGen 已不存在
-XX:MaxPermSize 删除,改用 Metaspace
-XX:+UseConcMarkSweepGC 删除,CMS 已移除
-XX:+CMSClassUnloadingEnabled 删除
-XX:+PrintGCDetails 改为 -Xlog:gc*
-XX:+PrintGCDateStamps 改为 -Xlog 装饰器
-Xloggc:/path/gc.log 改为 -Xlog:gc*:file=...
-XX:+UseGCLogFileRotation 改为 -Xlogfilecount
-XX:NumberOfGCLogFiles 改为 filecount
-XX:GCLogFileSize 改为 filesize

8.2 推荐的 JDK 17 基础参数

如果服务部署在普通虚拟机或物理机,且希望堆内存固定,可以使用:

JAVA_OPTS="
  -Xms2g
  -Xmx2g
  -XX:MetaspaceSize=256m
  -XX:MaxMetaspaceSize=512m
  -XX:+HeapDumpOnOutOfMemoryError
  -XX:HeapDumpPath=/data/dumps
  -XX:ErrorFile=/data/logs/hs_err_pid%p.log
  -Xlog:gc*,safepoint:file=/data/logs/gc-%p-%t.log:time,uptime,level,tags:filecount=10,filesize=100M
"

说明:

  • -Xms / -Xmx 固定堆大小,适合容量稳定的服务。
  • MetaspaceSize 是元空间初始触发阈值,不等于固定占用。
  • MaxMetaspaceSize 不建议盲目设太小,否则类多的服务容易 OOM。
  • HeapDumpPath 建议指向独立磁盘目录,避免打满应用日志盘。
  • ErrorFile 可以保留 JVM 崩溃日志。
  • -Xlog:gc*,safepoint 同时记录 GC 和安全点停顿。

8.3 容器环境参数

如果服务运行在 Docker / Kubernetes 里,不建议同时强依赖固定 -Xmx 和容器内存百分比。两种方式选一种。

方式一:固定堆,适合资源非常明确的服务:

JAVA_OPTS="
  -Xms1536m
  -Xmx1536m
  -XX:MetaspaceSize=256m
  -XX:MaxMetaspaceSize=512m
  -XX:+HeapDumpOnOutOfMemoryError
  -XX:HeapDumpPath=/data/dumps
  -Xlog:gc*,safepoint:file=/data/logs/gc-%p-%t.log:time,uptime,level,tags:filecount=10,filesize=100M
"

方式二:使用容器内存百分比,适合镜像在不同规格容器中复用:

JAVA_OPTS="
  -XX:InitialRAMPercentage=50.0
  -XX:MaxRAMPercentage=75.0
  -XX:MinRAMPercentage=50.0
  -XX:MetaspaceSize=256m
  -XX:MaxMetaspaceSize=512m
  -XX:+HeapDumpOnOutOfMemoryError
  -XX:HeapDumpPath=/data/dumps
  -Xlog:gc*,safepoint:file=/data/logs/gc-%p-%t.log:time,uptime,level,tags:filecount=10,filesize=100M
"

说明:

  • JDK 17 默认支持容器内存感知,通常不需要显式加 -XX:+UseContainerSupport
  • 容器里不要把 MaxRAMPercentage 设到 90% 以上,必须给 direct memory、metaspace、线程栈、JIT、native 内存留空间。
  • 如果容器 CPU quota 和 JVM 识别不一致,可临时使用 -XX:ActiveProcessorCount=4 固定 JVM 看到的 CPU 数。

8.4 G1 与 ZGC 怎么选

JDK 17 默认 GC 通常是 G1。大多数微服务可以先使用 G1,不要一上来就切 ZGC。

G1 示例:

JAVA_OPTS="
  -XX:+UseG1GC
  -XX:MaxGCPauseMillis=200
  -XX:InitiatingHeapOccupancyPercent=45
  -Xlog:gc*,safepoint:file=/data/logs/gc-%p-%t.log:time,uptime,level,tags:filecount=10,filesize=100M
"

ZGC 示例:

JAVA_OPTS="
  -XX:+UseZGC
  -Xlog:gc*,safepoint:file=/data/logs/gc-%p-%t.log:time,uptime,level,tags:filecount=10,filesize=100M
"

选择建议:

场景 建议
普通接口服务、网关、后台管理 先用 G1
堆内存较大、延迟极敏感 压测后评估 ZGC
追求吞吐、批处理任务 先压测 G1,再考虑 Parallel GC
没有 GC 观测能力 不要贸然切换 GC

不要同时配置:

# 错误示例
-XX:+UseG1GC -XX:+UseZGC

8.5 模块反射参数 --add-opens

JDK 17 对强封装更严格。升级后有些老框架或工具可能报:

Unable to make field private final ... accessible:
module java.base does not "opens java.lang" to unnamed module

临时兼容可以加:

--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.util=ALL-UNNAMED
--add-opens java.base/java.math=ALL-UNNAMED

但这只能作为过渡方案。正确做法是升级触发反射问题的依赖,例如序列化框架、字节码工具、老 ORM 插件、老 JSON 工具。

8.6 完整启动脚本示例

普通 Linux 服务:

#!/usr/bin/env bash

APP_NAME=order-service
APP_HOME=/data/apps/${APP_NAME}
LOG_DIR=/data/logs/${APP_NAME}
DUMP_DIR=/data/dumps/${APP_NAME}

mkdir -p "${LOG_DIR}" "${DUMP_DIR}"

JAVA_HOME=/usr/lib/jvm/java-17-openjdk
PATH="${JAVA_HOME}/bin:${PATH}"

JAVA_OPTS="
  -Xms2g
  -Xmx2g
  -XX:+UseG1GC
  -XX:MaxGCPauseMillis=200
  -XX:MetaspaceSize=256m
  -XX:MaxMetaspaceSize=512m
  -XX:+HeapDumpOnOutOfMemoryError
  -XX:HeapDumpPath=${DUMP_DIR}
  -XX:ErrorFile=${LOG_DIR}/hs_err_pid%p.log
  -Xlog:gc*,safepoint:file=${LOG_DIR}/gc-%p-%t.log:time,uptime,level,tags:filecount=10,filesize=100M
"

APP_OPTS="
  --spring.profiles.active=prod
  --server.port=8080
"

exec java ${JAVA_OPTS} -jar "${APP_HOME}/${APP_NAME}.jar" ${APP_OPTS}

容器启动示例:

JAVA_OPTS="
  -XX:InitialRAMPercentage=50.0
  -XX:MaxRAMPercentage=75.0
  -XX:+UseG1GC
  -XX:MaxGCPauseMillis=200
  -XX:+HeapDumpOnOutOfMemoryError
  -XX:HeapDumpPath=/data/dumps
  -Xlog:gc*,safepoint:file=/data/logs/gc-%p-%t.log:time,uptime,level,tags:filecount=10,filesize=100M
"

exec java ${JAVA_OPTS} -jar /app/app.jar

8.7 JVM 参数验证

启动后检查 JVM 实际参数:

jps -l
jcmd <pid> VM.version
jcmd <pid> VM.flags
jcmd <pid> VM.command_line
jcmd <pid> GC.heap_info

查看 GC 日志是否生成:

ls -lh /data/logs/order-service/
grep -nE "Using G1|Pause|Safepoint|Heap" /data/logs/order-service/gc-*.log | head -50

如果服务部署在容器里,还要确认容器内存限制:

cat /sys/fs/cgroup/memory.max 2>/dev/null || true
cat /sys/fs/cgroup/memory/memory.limit_in_bytes 2>/dev/null || true

九、Spring Security 6 迁移

Spring Security 是升级中最容易出问题的部分。建议先理解基本概念:认证负责“你是谁”,授权负责“你能访问什么”。OAuth2 中授权服务器负责发 token,资源服务器负责校验 token。你可以参考这篇补充阅读:Spring Security + OAuth2 参考文章

9.1 旧写法替换表

旧写法 新写法
WebSecurityConfigurerAdapter 定义 SecurityFilterChain Bean
authorizeRequests() authorizeHttpRequests()
antMatchers() requestMatchers()
csrf().ignoringAntMatchers() csrf().ignoringRequestMatchers()
@EnableGlobalMethodSecurity @EnableMethodSecurity
ResourceServerConfigurerAdapter oauth2ResourceServer()
spring-security-oauth Spring Authorization Server + Resource Server

9.2 普通资源服务配置

order-service 为例,如果它只负责校验 Bearer token:

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

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>

JWT 资源服务器:

@Configuration
@EnableMethodSecurity
public class ResourceServerSecurityConfig {

    @Bean
    SecurityFilterChain apiSecurityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers("/actuator/health", "/error").permitAll()
                        .requestMatchers(HttpMethod.GET, "/api/orders/public/**").permitAll()
                        .anyRequest().authenticated())
                .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .csrf(AbstractHttpConfigurer::disable)
                .formLogin(AbstractHttpConfigurer::disable)
                .httpBasic(AbstractHttpConfigurer::disable);
        return http.build();
    }
}

配置:

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: http://sso:9000

Opaque token 资源服务器:

@Configuration
@EnableMethodSecurity
public class ResourceServerSecurityConfig {

    @Bean
    SecurityFilterChain apiSecurityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers("/actuator/health", "/error").permitAll()
                        .anyRequest().authenticated())
                .oauth2ResourceServer(oauth2 -> oauth2
                        .opaqueToken(Customizer.withDefaults()))
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .csrf(AbstractHttpConfigurer::disable);
        return http.build();
    }
}

配置:

spring:
  security:
    oauth2:
      resourceserver:
        opaquetoken:
          introspection-uri: http://sso:9000/oauth2/introspect
          client-id: order-service
          client-secret: order-service-secret

如果系统强依赖“登出立即失效、踢下线、单设备登录、实时撤销”,第一阶段更适合 opaque token 或 reference token。JWT 也能做撤销,但需要黑名单、缓存同步或短有效期策略,复杂度更高。

9.3 URL 权限迁移注意点

旧代码:

http.authorizeRequests()
        .antMatchers("/login", "/captcha").permitAll()
        .antMatchers("/admin/**").hasRole("ADMIN")
        .anyRequest().authenticated();

新代码:

http.authorizeHttpRequests(authorize -> authorize
        .requestMatchers("/login", "/captcha").permitAll()
        .requestMatchers("/admin/**").hasRole("ADMIN")
        .anyRequest().authenticated());

注意:

  • 白名单不能机械复制,要确认路径是否包含 context-path、gateway 前缀、版本前缀。
  • requestMatchers 会根据应用环境选择 matcher,不要默认以为它和旧 antMatchers 所有行为完全一致。
  • permitAll 接口要做清单化评审,尤其是 /api/**/actuator/**、上传下载接口。

十、OAuth2 授权服务器迁移

老项目常见结构是:

sso
  OauthServerConfig extends AuthorizationServerConfigurerAdapter
  SecurityConfig extends WebSecurityConfigurerAdapter
  RedisTokenStore
  TokenEnhancer

Boot 3 / Security 6 后,建议切换成:

sso
  AuthorizationServerConfig
  RegisteredClientRepository
  OAuth2AuthorizationService
  OAuth2TokenCustomizer
  SecurityFilterChain

10.1 授权服务器依赖

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

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-oauth2-authorization-server</artifactId>
</dependency>

10.2 基础配置示例

以下示例使用 .with() DSL,需要 Spring Security 6.4+(对应 Spring Boot 3.4+)。如果使用 Boot 3.2/3.3,请改用 http.apply(authorizationServerConfigurer) 方式。

@Configuration
public class AuthorizationServerConfig {

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
        OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
                OAuth2AuthorizationServerConfigurer.authorizationServer();

        http
                .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher())
                .with(authorizationServerConfigurer, authorizationServer ->
                        authorizationServer.oidc(Customizer.withDefaults()))
                .authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated())
                .csrf(csrf -> csrf.ignoringRequestMatchers(
                        authorizationServerConfigurer.getEndpointsMatcher()));

        return http.build();
    }

    @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers("/actuator/health").permitAll()
                        .anyRequest().authenticated())
                .formLogin(Customizer.withDefaults());
        return http.build();
    }

    @Bean
    AuthorizationServerSettings authorizationServerSettings() {
        return AuthorizationServerSettings.builder()
                .issuer("http://sso:9000")
                .build();
    }
}

10.3 客户端注册

示例使用内存注册,生产建议落库:

@Bean
RegisteredClientRepository registeredClientRepository(PasswordEncoder passwordEncoder) {
    RegisteredClient orderService = RegisteredClient.withId(UUID.randomUUID().toString())
            .clientId("order-service")
            .clientSecret(passwordEncoder.encode("order-service-secret"))
            .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
            .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
            .scope("order.read")
            .scope("order.write")
            .tokenSettings(TokenSettings.builder()
                    .accessTokenTimeToLive(Duration.ofHours(2))
                    .build())
            .build();

    return new InMemoryRegisteredClientRepository(orderService);
}

10.4 Password Grant 怎么办

很多老系统还在使用:

grant_type=password
username=...
password=...
client_id=...
client_secret=...

这个模式不建议继续扩散。新系统应优先考虑:

  • 前端用户登录:Authorization Code + PKCE。
  • 服务间调用:Client Credentials。
  • 内部兼容接口:短期保留自定义 password grant,明确下线计划。

如果必须兼容老客户端,可以通过自定义 AuthenticationConverterAuthenticationProvider 扩展授权类型。这个方案要明确为“过渡兼容”,不要作为新系统标准登录方案。

10.5 Token 存储和撤销

老代码经常直接注入 TokenStore

OAuth2AccessToken token = tokenStore.readAccessToken(accessToken);
tokenStore.removeAccessToken(token);

升级后建议收口成接口:

public interface TokenLifecycleService {
    boolean isActive(String accessToken);

    boolean revoke(String accessToken);

    Optional<UserPrincipal> loadPrincipal(String accessToken);
}

业务服务只依赖 TokenLifecycleService,不要依赖 Redis key、OAuth2Authorization 内部结构或具体 token 存储实现。这样以后从 opaque token 切 JWT,或从 Redis 切 JDBC,业务层不用跟着大改。

10.6 登录和访问流程

order-service Token Store Authorization Server sso Client order-service Token Store Authorization Server sso Client 登录或客户端认证 校验用户/客户端 保存授权和 token access_token Authorization: Bearer access_token introspection 或获取公钥校验 token active / claims 业务响应

十一、Spring Cloud / Gateway / Nacos

11.1 Spring Cloud 版本

如果使用 Spring Boot 3.5.x,Spring Cloud 应选择对应的 2025.0.x 版本线。

父 POM 示例:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

11.2 Spring Cloud Alibaba / Nacos

Spring Cloud Alibaba 也要与 Spring Cloud 版本线匹配。

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>${spring-cloud-alibaba.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

使用 Nacos 配置中心和注册中心需要显式引入对应 starter:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

Boot 3 以后应优先使用 Spring Boot Config Data 的 spring.config.import 机制。Spring Cloud Alibaba 2025.x 官方文档要求通过 spring.config.import 接入 Nacos 配置中心;2025.1.x 已明确废弃 Spring Cloud Bootstrap,不再支持通过 bootstrap.yml / bootstrap.properties 接入 Nacos。

示例:

spring:
  application:
    name: order-service
  cloud:
    nacos:
      server-addr: 127.0.0.1:8848
      username: ${NACOS_USERNAME:nacos}
      password: ${NACOS_PASSWORD:nacos}
  config:
    import:
      - optional:nacos:order-service.yaml?group=DEFAULT_GROUP&refreshEnabled=true
      - optional:nacos:common-config.properties?group=DEFAULT_GROUP&refreshEnabled=true

optional:nacos:nacos: 的区别要明确:

写法 含义 适用场景
optional:nacos:common-config.properties Nacos 配置不存在或拉取失败时不阻断启动 本地开发、非核心公共配置、允许降级的配置
nacos:order-service.yaml Nacos 配置拉取失败时快速失败,应用启动失败 生产必需配置,例如数据库、Redis、鉴权、路由

老版本经常这样配置扩展配置:

# 旧写法:bootstrap.yml
spring:
  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848
        namespace: standard-104
        prefix: sso
        file-extension: yml
        group: DEFAULT_GROUP
        extension-configs:
          - data-id: common-config.properties
            group: DEFAULT_GROUP
            refresh: true
          - data-id: application-share.yml
            group: DEFAULT_GROUP
            refresh: true

新版本迁移到 application.yml

# 新写法:application.yml
spring:
  application:
    name: sso
  cloud:
    nacos:
      server-addr: 127.0.0.1:8848
      username: ${NACOS_USERNAME:nacos}
      password: ${NACOS_PASSWORD:nacos}
      config:
        namespace: standard-104
        group: DEFAULT_GROUP
      discovery:
        namespace: standard-104
  config:
    import:
      - optional:nacos:common-config.properties?group=DEFAULT_GROUP&refreshEnabled=true
      - optional:nacos:application-share.yml?group=DEFAULT_GROUP&refreshEnabled=true
      - optional:nacos:sso.yml?group=DEFAULT_GROUP&refreshEnabled=true

如果旧配置使用的是 shared-configs,迁移方式相同:

spring:
  config:
    import:
      - optional:nacos:shared-redis.properties?group=DEFAULT_GROUP&refreshEnabled=true
      - optional:nacos:shared-security.properties?group=DEFAULT_GROUP&refreshEnabled=true

如果一个服务同时加载服务私有配置和公共配置,建议把“公共配置在前、服务配置在后”写清楚,后面的 import 优先级更高,便于服务私有配置覆盖公共默认值:

spring:
  config:
    import:
      - optional:nacos:common-config.properties?group=DEFAULT_GROUP&refreshEnabled=true
      - optional:nacos:order-service.yaml?group=DEFAULT_GROUP&refreshEnabled=true

11.3 本地配置文件是否需要调整

本地 application.ymlapplication-{profile}.yml 仍然会按 Spring Boot 标准规则加载,不需要因为接入 Nacos 就删除。本地配置一般保留这些内容:

  • spring.application.name
  • spring.profiles.active 或由启动参数指定 profile
  • Nacos server、namespace、group、鉴权占位符
  • spring.config.import
  • 本地开发兜底配置

需要调整的是两类配置:

原方式 是否调整 建议
bootstrap.yml / bootstrap.properties 接入 Nacos 需要 迁移到 application.ymlspring.config.import
extension-configs / shared-configs 需要 改为多条 optional:nacos:nacos: import
普通 application.yml 本地业务配置 不一定 可保留,但要确认是否应被 Nacos 覆盖
额外本地文件,例如 local.properties 视情况 optional:file:optional:classpath: 显式导入

额外本地文件示例:

spring:
  config:
    import:
      - optional:file:./config/local.properties
      - optional:classpath:local-default.yml
      - optional:nacos:common-config.properties?group=DEFAULT_GROUP&refreshEnabled=true
      - optional:nacos:order-service.yaml?group=DEFAULT_GROUP&refreshEnabled=true

这里的关键点是优先级。Spring Boot 官方说明,spring.config.import 导入的配置会作为额外配置文档插入,导入配置的值会优先于声明它的本地文件;同一个 spring.config.import 下多个位置按声明顺序处理,后面的 import 优先级更高。因此要在升级前确认:

  • 哪些配置必须由 Nacos 覆盖本地默认值。
  • 哪些本地配置只是开发兜底,不能进入生产镜像。
  • 生产环境是否允许 optional:nacos: 降级启动;核心配置通常更适合使用 nacos: 快速失败。
  • profile 维度配置是否仍按预期加载,例如 order-service-dev.yamlorder-service-prod.yaml

注意点:

  • 不要在子模块里再强行锁旧 nacos-api
  • 如果公司 Nacos Server 版本较老,要先验证 client 兼容性。
  • bootstrap.ymlapplication.yml 的迁移要做配置加载快照,确认 namespace、group、dataId、profile 优先级不变。
  • 配置中心、注册中心、动态路由、灰度配置都要单独回归。
  • Nacos 用户名、密码、token、namespace 不要写入博客、代码库或普通日志。

11.4 Gateway 代码适配

Spring 版本升级后,一些 API 会变化。例如请求方法读取应避免使用旧 API:

// before
String method = request.getMethodValue();

// after
String method = request.getMethod().name();

Gateway 还需要重点验证:

  • 全局过滤器是否还能读取和回写 request body。
  • 响应体日志过滤器是否存在 DataBuffer 泄漏。
  • CORS 配置是否变化。
  • 路由谓词和过滤器配置是否还能从 Nacos 动态刷新。
  • 鉴权 header 是否正确透传到下游服务。

十二、MyBatis / MyBatis-Plus / PageHelper

Spring Boot 3 对应 Spring 6,MyBatis 相关依赖也要同步。

示例:

<properties>
    <mybatis-plus.version>3.5.9</mybatis-plus.version>
    <mybatis-spring.version>3.0.4</mybatis-spring.version>
</properties>

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>${mybatis-plus.version}</version>
</dependency>

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-jsqlparser</artifactId>
    <version>${mybatis-plus.version}</version>
</dependency>

MyBatis-Plus 分页插件相关能力在新版本中拆分过,分页场景要确认 mybatis-plus-jsqlparser 是否已引入。

如果最终进入 Spring Boot 4,要重新确认 MyBatis-Plus、MyBatis-Spring、PageHelper 是否已经声明支持 Boot 4 / Spring Framework 7。以 MyBatis-Plus 为例,Boot 4 场景建议选择官方安装文档中明确支持 Spring Boot 4 的版本线,不要沿用 Boot 3 阶段临时验证通过的旧版本。

旧包名也要检查:

旧包 新包
com.baomidou.mybatisplus.mapper com.baomidou.mybatisplus.core.mapper
com.baomidou.mybatisplus.annotations com.baomidou.mybatisplus.annotation

十三、中间件与三方 SDK 专项检查

Spring Boot 升级通常不是单个框架升级,而是一次依赖链升级。微服务里最容易出事故的地方不是 Controller,而是 Redis、Kafka、数据库、连接池、对象存储、gRPC、SocketIO、日志和监控这些“平时不怎么改、线上一直跑”的组件。

建议把中间件拆成专项验证,而不是只在最后跑一遍接口。

13.1 Redis、Redisson、Jedis、Lettuce

Redis 迁移要关注两类问题:客户端兼容和数据格式兼容。

扫描 Redis 客户端依赖:

find . -name "pom.xml" -print0 \
  | xargs -0 grep -nE "spring-boot-starter-data-redis|redisson|jedis|lettuce"

重点检查:

  • spring-boot-starter-data-redis 由 Boot BOM 管理,不要在子模块里锁旧 spring-data-redis
  • Redisson starter 要选择与 Boot 3/4 对齐的版本线;Boot 4 场景还要确认 starter 是否支持 Spring Framework 7。
  • Jedis、Lettuce 不要混用到无法解释。能统一就统一,不能统一就明确每个模块的客户端边界。
  • Redis key 命名、TTL、序列化格式不能因为包名从 javax 迁移到 jakarta 或类名变化而悄悄改变。
  • 分布式锁、延迟队列、限流器、缓存穿透保护要做并发回归。

建议补一组兼容测试:

验证项 怎么验证
Redis value 序列化 老版本写入,新版本读取;新版本写入,老版本如需回滚也能读取
Redis lock 并发抢锁、锁续期、锁释放、异常释放
TTL 写入后检查过期时间是否与旧版本一致
Pub/Sub 或 Stream 消息格式、消费确认、重试语义不变

常用检查命令:

redis-cli -h 127.0.0.1 -p 6379 info clients
redis-cli -h 127.0.0.1 -p 6379 info memory
redis-cli -h 127.0.0.1 -p 6379 --scan --pattern "order:*" | head -20

13.2 Kafka 与 Spring Kafka

Spring Kafka 跨大版本升级时,不能只看应用能不能启动,还要验证 broker、topic、序列化、消费者组、重试、死信和幂等。

扫描 Kafka 依赖:

find . -name "pom.xml" -print0 \
  | xargs -0 grep -nE "spring-kafka|kafka-clients|kafka-streams"

建议确认:

  • Spring Kafka 版本由 Spring Boot BOM 或明确兼容矩阵管理。
  • Kafka broker 是否允许同步升级;应用升级和 broker 升级最好拆成不同窗口。
  • Kafka 4.x 环境应评估 KRaft 模式,确认是否仍依赖 ZooKeeper。
  • topic 名称、key/value 序列化、header、分区策略、消费位点、重试和死信 topic 不变。
  • 消费者组不要因配置项改名而变更,否则可能导致重复消费或漏消费。

常用检查命令:

kafka-topics.sh --bootstrap-server localhost:9092 --list
kafka-consumer-groups.sh --bootstrap-server localhost:9092 --list
kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group order-service

如果项目使用 JSON 消息,建议把典型消息保存为样本:

kafka-console-consumer.sh \
  --bootstrap-server localhost:9092 \
  --topic order-created \
  --from-beginning \
  --max-messages 10 > kafka-order-created-sample.json

升级后重新采集,对比字段、类型、空值、枚举值、时间格式是否一致。

13.3 数据库、连接池和 ORM

数据库升级要保守。对于 MySQL,生产上更建议选择 8.4 LTS 这类长期支持版本,而不是直接跳到创新版。国产数据库或商业数据库则必须以厂商兼容说明和 JDBC 驱动版本为准。

扫描数据库相关依赖:

find . -name "pom.xml" -print0 \
  | xargs -0 grep -nE "mysql-connector|mariadb|postgresql|dmjdbc|druid|HikariCP|mybatis|pagehelper"

重点检查:

  • Spring Boot 默认偏向 HikariCP,若继续使用 Druid,要确认 Druid 版本支持 Jakarta / Boot 3/4。
  • 数据库驱动版本不能落后到不支持 JDK 17。
  • 事务传播、批量插入、分页 SQL、乐观锁、逻辑删除、防全表更新插件要回归。
  • SQL 方言、时区、字符集、连接参数不能在升级中被默认值改变。

建议最少补这些测试:

场景 风险
分页查询 PageHelper / MyBatis-Plus 插件顺序变化
批量写入 JDBC rewrite、事务边界、主键回填差异
时间字段 LocalDateTime、时区、JSON 格式变化
事务回滚 @Transactional 代理、异常类型变化
连接池 最大连接、空闲连接、超时、泄漏检测配置变化

13.4 FastDFS、OSS、S3 和对象存储 SDK

文件链路往往既涉及依赖版本,又涉及业务契约。升级时要保持:

  • 上传后的 URL 格式不变,或提供兼容转换。
  • 文件路径、bucket、ACL、content-type、content-disposition 不变。
  • 分片上传、断点续传、删除、预签名 URL 有完整回归。
  • 下载接口不能出现路径穿越、任意文件读取、SSRF。

如果还在使用 AWS SDK for Java 1.x,应规划迁移到 AWS SDK for Java 2.x。AWS 官方已经宣布 1.x 进入维护并结束支持路线,不适合作为长期安全基线。迁移时要重点处理:

  • client 构造方式变化。
  • 凭证提供链变化。
  • 同步 / 异步 client 差异。
  • 请求超时、连接池和重试策略。
  • 异常类型变化。

13.5 gRPC、SocketIO 与 Netty 冲突

gRPC、SocketIO、Redisson、Gateway 都可能传递 Netty。升级 Spring Boot 后,最常见的问题是同一进程里出现多条 Netty 版本线。

检查命令:

mvn -DskipTests dependency:tree > dependency-tree.txt
grep -nE "netty|grpc|socketio|redisson|reactor-netty" dependency-tree.txt

重点验证:

  • 长连接建立、鉴权、心跳、断线重连。
  • 消息 ack、超时、重试、顺序性。
  • 高并发连接数和内存释放。
  • Netty 版本是否被某个旧 starter 强行拉低。
  • Gateway、gRPC、SocketIO 是否部署在同一进程;如果同进程冲突难以处理,优先拆服务边界,而不是在依赖树里硬压版本。

十四、文件上传迁移与安全加固

文件上传是 Boot 2 升级到 3/4 时经常被低估的风险点。老项目里常见 CommonsMultipartResolverCommonsMultipartFile、Commons FileUpload 1.x。进入 Jakarta 时代后,这条链路要重新评估。

14.1 先扫描旧上传链路

find . -name "*.java" -print0 \
  | xargs -0 grep -nE "CommonsMultipartResolver|CommonsMultipartFile|DiskFileItem|ServletFileUpload|FileUploadException"

find . -name "pom.xml" -print0 \
  | xargs -0 grep -nE "commons-fileupload|commons-io"

如果只是普通 Web 上传,优先使用 Spring Boot 标准 multipart:

spring:
  servlet:
    multipart:
      enabled: true
      max-file-size: 50MB
      max-request-size: 60MB
      file-size-threshold: 2MB
      location: /data/tmp/upload

Controller 示例:

@RestController
@RequestMapping("/files")
public class FileUploadController {

    @PostMapping("/upload")
    public FileUploadResponse upload(@RequestPart("file") MultipartFile file) throws IOException {
        if (file.isEmpty()) {
            throw new IllegalArgumentException("file is empty");
        }
        String originalFilename = StringUtils.cleanPath(
                Objects.requireNonNullElse(file.getOriginalFilename(), "unknown"));
        if (originalFilename.contains("..")) {
            throw new IllegalArgumentException("invalid file name");
        }
        try (InputStream inputStream = file.getInputStream()) {
            // 保存到对象存储、文件服务或临时目录
            return new FileUploadResponse(originalFilename, file.getSize());
        }
    }
}

14.2 不要只改类型,要保留上传契约

旧代码可能依赖 CommonsMultipartFile 的具体行为,例如临时文件路径、content-type、原始文件名、重复读取流。迁移时要列出行为契约:

契约 检查方式
最大文件大小 超限时 HTTP 状态码、错误码、错误信息是否与旧版本兼容
文件名 中文名、空格、特殊字符、超长文件名
MIME 前端传入 content-type 与后端二次识别结果
临时目录 容器内目录是否可写,磁盘满时是否有告警
文件流 是否允许重复读取,是否及时关闭
返回值 URL、fileId、bucket、path、size、hash 字段是否不变

14.3 上传安全底线

文件上传至少要做这些安全控制:

  • 限制文件大小和请求总大小。
  • 使用白名单校验扩展名和 MIME,不要只相信浏览器传入的 Content-Type
  • 使用服务端生成的存储文件名,不要直接使用用户原始文件名作为物理路径。
  • 防路径穿越:拒绝 ../、绝对路径、控制字符。
  • 对公网可访问文件设置合理 ACL,避免默认公开。
  • 对压缩包、Office、图片等高风险格式按企业要求接入病毒扫描或内容安全检测。
  • 下载和预览接口要防 SSRF,不能让用户传任意 URL 让服务端去拉取。

十五、其他常见迁移项

15.1 spring.factories 自动配置注册迁移

Spring Boot 3 废弃了 META-INF/spring.factories 中的自动配置注册方式。如果项目有自定义 starter 或自动配置类,必须迁移。

旧方式(Boot 2):

# META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.example.MyAutoConfiguration,\
  com.example.AnotherAutoConfiguration

新方式(Boot 3):

创建文件 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

com.example.MyAutoConfiguration
com.example.AnotherAutoConfiguration

扫描命令:

find . -path "*/META-INF/spring.factories" -print0 \
  | xargs -0 grep -l "EnableAutoConfiguration"

注意:spring.factories 中的其他 key(如 ApplicationListenerEnvironmentPostProcessor)在 Boot 3 中仍然支持,只有 EnableAutoConfiguration 必须迁移。

15.2 Spring Cloud Sleuth → Micrometer Tracing

Spring Cloud Sleuth 在 Spring Cloud 2022.0(对应 Boot 3)后不再维护,链路追踪能力迁移到 Micrometer Tracing。

扫描旧依赖:

find . -name "pom.xml" -print0 \
  | xargs -0 grep -nE "spring-cloud-starter-sleuth|spring-cloud-sleuth"

替换方式:

<!-- 删除 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>

<!-- 替换为 -->
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>

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

如果使用 Zipkin 上报:

<dependency>
    <groupId>io.zipkin.reporter2</groupId>
    <artifactId>zipkin-reporter-brave</artifactId>
</dependency>

配置变化:

# Boot 2 + Sleuth
spring:
  sleuth:
    sampler:
      probability: 1.0

# Boot 3 + Micrometer Tracing
management:
  tracing:
    sampling:
      probability: 1.0

15.3 Apache HttpClient 4 → HttpClient 5

Spring Boot 3 默认使用 Apache HttpClient 5。如果项目通过 RestTemplate 自定义了 HttpComponentsClientHttpRequestFactory,需要调整。

扫描:

find . -name "*.java" -print0 \
  | xargs -0 grep -nE "org\.apache\.http\.|HttpClientBuilder|CloseableHttpClient|HttpComponentsClientHttpRequestFactory"

主要变化:

Boot 2 (HttpClient 4) Boot 3 (HttpClient 5)
org.apache.http.impl.client.HttpClientBuilder org.apache.hc.client5.http.impl.classic.HttpClientBuilder
org.apache.http.client.config.RequestConfig org.apache.hc.client5.http.config.RequestConfig
org.apache.http.impl.conn.PoolingHttpClientConnectionManager org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager

如果项目大量使用 HttpClient 4 API,可以临时保留旧版本依赖,但要规划迁移。

15.4 @ConstructorBinding 变化

Boot 3 中,如果配置属性类只有一个构造器,不再需要显式标注 @ConstructorBinding。多构造器场景仍需标注。

// Boot 2:必须标注
@ConstructorBinding
@ConfigurationProperties(prefix = "app.order")
public class OrderProperties {
    private final String prefix;
    private final int timeout;

    public OrderProperties(String prefix, int timeout) {
        this.prefix = prefix;
        this.timeout = timeout;
    }
}

// Boot 3:单构造器可省略 @ConstructorBinding
@ConfigurationProperties(prefix = "app.order")
public class OrderProperties {
    private final String prefix;
    private final int timeout;

    public OrderProperties(String prefix, int timeout) {
        this.prefix = prefix;
        this.timeout = timeout;
    }
}

同时注意:Boot 3 中 @ConstructorBindingorg.springframework.boot.context.properties.ConstructorBinding 移到了 org.springframework.boot.context.properties.bind.ConstructorBinding

15.5 Actuator 端点变化

Boot 3 对 Actuator 有几个重要变化:

  • /actuator/env POST 端点默认关闭。
  • management.endpoints.web.exposure.include 默认只暴露 health
  • management.server.base-path 替代旧的 management.server.servlet.context-path
  • Spring Boot 3.4+ 引入 management.endpoints.access.default=none 替代 management.endpoints.enabled-by-default=false

建议在升级后显式配置暴露策略:

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  endpoint:
    health:
      show-details: when-authorized

十六、配置项和启动项检查

16.1 配置属性迁移

Spring Boot 官方提供了 spring-boot-properties-migrator,可以在升级初期临时使用:

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

注意:

  • 它只适合迁移阶段临时使用。
  • 看到启动日志提示后,应手动修改配置。
  • 修改完成后删除该依赖。

16.2 常见配置检查

find . \( -name "*.yml" -o -name "*.yaml" -o -name "*.properties" \) -print0 \
  | xargs -0 grep -nE "management\.endpoints|management\.endpoint|server\.servlet|spring\.security|spring\.cloud|nacos|logging|redis"

重点检查:

  • Actuator 暴露端点是否符合安全要求。
  • Nacos 配置前缀、命名空间、group 是否正确。
  • Redis 序列化是否因类包名变化出现兼容问题。
  • 日志配置是否仍然兼容 Logback / Log4j2 当前版本。

十七、编译、启动和回归验证

17.1 编译验证

先验证父工程:

mvn -N -DskipTests validate

再编译公共模块:

mvn -pl common -am -DskipTests clean compile

再编译核心服务:

mvn -pl sso,gateway,order-service,user-service -am -DskipTests clean compile

最后跑测试:

mvn test

如果项目很大,可以按模块分批跑,不要一开始就全量编译到看不出问题来源。

17.2 依赖验证

mvn -DskipTests dependency:tree > dependency-tree.txt

grep -nE "spring-security|spring-web|spring-core|jakarta|javax|tomcat|undertow|nacos|spring-cloud" dependency-tree.txt

17.3 服务启动验证

至少启动:

  • sso
  • gateway
  • 一个典型资源服务,例如 order-service
  • 一个依赖数据库和 Redis 的服务,例如 user-service

常用检查:

curl -i http://localhost:9000/actuator/health
curl -i http://localhost:8080/actuator/health
curl -i http://localhost:8081/actuator/health

17.4 安全链路验证

场景 预期
不带 token 访问保护接口 返回 401
带无效 token 访问保护接口 返回 401
带有效 token 访问保护接口 正常返回
权限不足访问接口 返回 403
登出后再访问 token 失效
重复登录踢下线 老 token 失效
网关转发 Bearer token 下游能识别
白名单接口 不需要 token

17.5 JVM 验证

jps -l
jcmd <pid> VM.flags
jcmd <pid> GC.heap_info
ls -lh /data/logs/order-service/

如果服务运行一段时间后出现 OOM,要先看:

  • heap dump 是否生成。
  • GC 日志是否完整。
  • 容器是否发生 OOMKilled。
  • metaspace 是否过小。
  • direct memory 或线程数是否异常。

17.6 常见编译/启动报错速查表

报错信息 原因 修复方式
package javax.servlet does not exist Jakarta 迁移未完成 javax.servlet.*jakarta.servlet.*
cannot find symbol: class WebSecurityConfigurerAdapter Spring Security 6 移除了该类 改为定义 SecurityFilterChain Bean
cannot find symbol: method antMatchers(String) Security 6 移除了 antMatchers 改为 requestMatchers()
cannot find symbol: class ResourceServerConfigurerAdapter 旧 OAuth2 库已移除 使用 spring-boot-starter-oauth2-resource-server
NoSuchMethodError: javax.servlet.http.HttpServletRequest.getMethod() 运行时仍加载了 javax.servlet-api 检查依赖树,排除旧 javax.servlet-api
Class org.springframework.cloud.bootstrap.BootstrapConfiguration not found Spring Cloud Bootstrap 已废弃 迁移到 spring.config.import 机制
Failed to configure a DataSource: 'url' attribute is not specified Nacos 配置未加载,数据源配置缺失 检查 spring.config.import 中 Nacos 配置是否正确
UnsatisfiedDependencyException: ... TokenStore TokenStore 接口已移除 迁移到 OAuth2AuthorizationService
java.lang.NoClassDefFoundError: sun/misc/BASE64Encoder JDK 17 移除了 sun.misc 内部 API 改用 java.util.Base64
module java.base does not "opens java.lang" to unnamed module JDK 17 强封装,反射访问被拒绝 添加 --add-opens 参数或升级触发反射的依赖
BeanDefinitionOverrideException Boot 3 默认禁止 Bean 覆盖 排查重复 Bean 定义,或临时设置 spring.main.allow-bean-definition-overriding=true
No qualifying bean of type 'XXAutoConfiguration' spring.factories 注册方式失效 迁移到 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
ClassNotFoundException: org.springframework.cloud.sleuth.* Sleuth 在 Boot 3 生态中已移除 迁移到 Micrometer Tracing
Caused by: java.lang.IllegalArgumentException: Unsupported class file major version 61 某些旧依赖不兼容 JDK 17 字节码 升级该依赖到支持 JDK 17 的版本
-XX:+UseConcMarkSweepGC ... Unrecognized VM option CMS GC 在 JDK 17 中已移除 删除 CMS 相关参数,使用 G1 或 ZGC

遇到编译错误时的排查顺序:

  1. 先看是不是 javaxjakarta 没改完。
  2. 再看是不是旧 Security / OAuth2 API。
  3. 然后看依赖树里是否有版本冲突(Spring 5 和 Spring 6 混用)。
  4. 最后看是不是 JDK 内部 API 或旧字节码问题。

十八、完整操作 Checklist

  • 确认目标版本矩阵:JDK、Boot、Cloud、Alibaba、Nacos、Security。
  • 如果最终目标是 Boot 4,明确 Boot 3.5 baseline 和 Boot 4 final 两阶段检查点。
  • 确认商业 Servlet 容器是否支持目标 Servlet 规范和 Spring Boot 版本。
  • 新建升级分支,锁定升级范围。
  • 本地、CI、Docker 镜像全部切到 JDK 17。
  • 父 POM 升级 Spring Boot 3.x。
  • 如进入 Boot 4,额外确认 Spring Framework 7、Jackson 3、Servlet 6.1、Cloud 2025.1.x。
  • 引入 Spring Cloud / Alibaba BOM。
  • 增加 Maven Enforcer 校验 JDK。
  • 清理 Spring、Security、Jackson、Tomcat、Undertow 等旧手写版本。
  • 精准迁移 Jakarta EE 包。
  • 清理 JDK 内部 API。
  • 调整 JVM 参数,删除 CMS / PermGen / JDK 8 GC 日志参数。
  • WebSecurityConfigurerAdapter 改为 SecurityFilterChain
  • authorizeRequests / antMatchers 改为 authorizeHttpRequests / requestMatchers
  • @EnableGlobalMethodSecurity 改为 @EnableMethodSecurity
  • 老 OAuth2 授权服务器迁移到 Spring Authorization Server。
  • 资源服务迁移到 spring-boot-starter-oauth2-resource-server
  • 业务代码不要直接依赖 TokenStore,抽象 token 生命周期服务。
  • Gateway、Nacos、OpenFeign、MyBatis 分别验证。
  • Redis、Kafka、数据库、对象存储、gRPC、SocketIO 分别验证。
  • 文件上传从 Commons FileUpload 旧链路迁移到标准 multipart 或兼容方案。
  • 生成 SBOM,执行 SCA、SAST、Secret Scan、容器扫描或企业安全扫描。
  • 建立 API、Redis、Kafka、Nacos、文件 URL 行为快照。
  • 运行依赖树、编译、单测、启动、接口回归和安全链路验证。
  • 灰度发布,重点观察登录、鉴权、网关、GC、容器内存、P95/P99、5xx。
  • 准备旧镜像和配置回滚方案。
  • 设置 7-14 天观察期,输出稳定性和遗留风险报告。

十九、参考资料

安全专项治理、行为等价验证、生产发布与灰度回滚的详细内容见配套文档:安全治理、行为验证与生产发布指南

资料优先:


附录 A:Boot 4 升级路线前瞻

本附录适用于最终目标是 Spring Boot 4.x 的团队。如果当前只升级到 3.x,可以作为前瞻评估参考。

A.1 为什么先把 3.5 当成内部检查点

截至 2026-05-13,Spring Boot 官方文档当前稳定线已经包含 4.0.x。Spring Boot 4 官方迁移指南明确建议:开始升级前先升级到最新可用的 3.5.x,再迁移到 4.0.x。

原因:Spring Boot 2.x 到 3.x 的核心变化是 JDK 17、Spring Framework 6、Jakarta EE、Spring Security 6;Spring Boot 3.5 到 4.0 的核心变化又变成 Spring Framework 7、Jakarta EE 11、Servlet 6.1、Tomcat 11 / Jetty 12.1、Jackson 3、Spring Cloud 2025.1.x 等。如果一步从 2.x 跳到 4.x,编译错误、依赖冲突、运行时行为差异会混在一起,排查成本很高。

更稳妥的路线:

Spring Boot 2.x 现状

升级到 2.7 最新补丁

Spring Boot 3.5 baseline

编译通过

启动通过

主流程回归通过

Spring Boot 4.x final

生产灰度和观察

要点:

  • Spring Boot 3.5 baseline 不一定是最终交付版本,它可以只是内部技术检查点。
  • 每个检查点都必须可编译、可启动、可回归,不能只停留在"依赖已经改完"。

A.2 Boot 3 和 Boot 4 的目标矩阵要分开写

不要在同一张表里混用 Boot 3.5 和 Boot 4 的依赖版本。建议写成两阶段矩阵:

阶段 Spring Boot Spring Cloud Spring Cloud Alibaba Servlet 容器 重点目标
阶段一 3.5.x 2025.0.x 2025.0.x Tomcat 10.1 / Jetty 12 等 Boot 3 支持容器 JDK 17、Jakarta、Security 6、OAuth2、Cloud 版本对齐
阶段二 4.0.x 2025.1.x 2025.1.x Tomcat 11 / Jetty 12.1 / Servlet 6.1+ 兼容容器 Spring Framework 7、Jackson 3、Boot 4 starter 与自动配置变化

如果使用商业 Servlet 容器(TongWeb、BES、WebSphere Liberty 等),不要默认它们能无缝承接 Boot 4。Boot 4 要求 Servlet 6.1+ 兼容容器,必须拿到厂商正式兼容说明、starter 版本、样例工程和问题响应机制,再决定是否纳入上线范围。

A.3 Boot 4 前瞻检查项

进入 Boot 4 前建议额外确认:

  • Spring Framework 7 对 Bean 生命周期、AOP 代理、条件注解的行为变化。
  • Jackson 3 对日期格式、枚举序列化、空值处理的默认行为变化。
  • Servlet 6.1 对 Filter、Listener、Session 的 API 变化。
  • Spring Cloud 2025.1.x 对 Gateway、OpenFeign、LoadBalancer 的兼容性。
  • MyBatis-Plus、Redisson、Nacos Client 是否已声明支持 Boot 4。
  • 容器镜像基础层是否需要升级(Tomcat 11、JDK 21+)。
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐