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 微服务项目可以参考下面的矩阵:

组件推荐目标线说明
JDK17+Spring Boot 3.x 硬性要求,建议先统一开发机、CI、镜像
Spring Boot3.5.x示例使用 3.5.x,实际以公司验证版本为准
Spring Cloud2025.0.x对应 Spring Boot 3.5.x 版本线
Spring Cloud Alibaba2025.0.x对应 Spring Cloud 2025.0.x / Boot 3.5.x
Nacos Client3.x需要与 Spring Cloud Alibaba 版本线匹配
Spring Security6.xBoot 3 自动管理
Spring Authorization Server1.x替代老的 Spring Security OAuth 授权服务器
MyBatis-Spring3.x适配 Spring 6 / Boot 3
MyBatis-Plus3.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 5Boot 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
ResourceServerConfigurerAdapteroauth2ResourceServer()
spring-security-oauthSpring 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.propertiesNacos 配置不存在或拉取失败时不阻断启动本地开发、非核心公共配置、允许降级的配置
nacos:order-service.yamlNacos 配置拉取失败时快速失败,应用启动失败生产必需配置,例如数据库、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.mappercom.baomidou.mybatisplus.core.mapper
com.baomidou.mybatisplus.annotationscom.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.HttpClientBuilderorg.apache.hc.client5.http.impl.classic.HttpClientBuilder
org.apache.http.client.config.RequestConfigorg.apache.hc.client5.http.config.RequestConfig
org.apache.http.impl.conn.PoolingHttpClientConnectionManagerorg.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 existJakarta 迁移未完成javax.servlet.*jakarta.servlet.*
cannot find symbol: class WebSecurityConfigurerAdapterSpring 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 foundSpring Cloud Bootstrap 已废弃迁移到 spring.config.import 机制
Failed to configure a DataSource: 'url' attribute is not specifiedNacos 配置未加载,数据源配置缺失检查 spring.config.import 中 Nacos 配置是否正确
UnsatisfiedDependencyException: ... TokenStoreTokenStore 接口已移除迁移到 OAuth2AuthorizationService
java.lang.NoClassDefFoundError: sun/misc/BASE64EncoderJDK 17 移除了 sun.misc 内部 API改用 java.util.Base64
module java.base does not "opens java.lang" to unnamed moduleJDK 17 强封装,反射访问被拒绝添加 --add-opens 参数或升级触发反射的依赖
BeanDefinitionOverrideExceptionBoot 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 optionCMS 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 BootSpring CloudSpring Cloud AlibabaServlet 容器重点目标
阶段一3.5.x2025.0.x2025.0.xTomcat 10.1 / Jetty 12 等 Boot 3 支持容器JDK 17、Jakarta、Security 6、OAuth2、Cloud 版本对齐
阶段二4.0.x2025.1.x2025.1.xTomcat 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+)。

更多推荐