最近在自己研究 Spring 体系底层架构升级时,我集中踩到了两类问题:一类来自 Spring Boot 4.0 的模块化重构,另一类来自 Spring Cloud 2025.1 在网关与客户端侧的架构调整。

本文涉及的主要依赖版本如下:

  • Java:21
  • Spring Boot:4.0.5
  • Spring Cloud:2025.1.1
  • Spring Cloud Alibaba:2025.1.0.0
  • Gradle:9.4.1

这次升级最明显的感受是,Spring 团队不再满足于“保持兼容的渐进演化”,而是开始更彻底地做模块边界收敛、语义归类和职责拆分。过去诸多“能用但不够干净”的 API、包路径和 Starter 结构,均被重新整理。

若应用中同时使用了 Spring MVC、Actuator、Kafka、Cache、Gateway、Feign 或自定义消息转换器,那么此次升级极易引发一连串编译错误、依赖缺失和行为变化。本文客观记录了我在升级过程中遇到的核心破坏性更新、根因定位及最终的修复方案。

版本基线与上下文

此次并非单纯升级 Spring Boot,而是将整条 Spring 技术栈的依赖基线一并抬升。我的升级前后核心版本对照如下:

组件 升级前 升级后
Java 17 21
Spring Boot 3.4.6 4.0.5
Spring Cloud 2024.0.1 2025.1.1
Spring Cloud Alibaba 2023.0.3.2 2025.1.0.0
Gradle 8.14.2 9.4.1

大量问题并非单一框架升级导致,而是基线抬高后的连锁反应。例如:

  • ErrorControllerWebMvcRegistrations 的报错偏向 Spring Boot 4 的模块化迁移。
  • Gateway Starter 重命名、Feign 解码链调整,偏向 Spring Cloud 2025.1 的架构拆分。
  • 测试运行时与插件兼容性,往往与 Java 21、Gradle 9 强相关。
  • 生态兼容问题受 Spring Cloud Alibaba 版本跨度影响。

Spring Boot 4 模块化拆分分析

Spring Boot 4 在架构上执行了更为激进的模块化拆分与领域对齐。以往混杂的自动配置类、指标接口和 Web 契约被重新划入明确的模块。升级后的诸多报错,本质上是框架对原有模糊依赖关系的强制纠偏。

核心类包路径变更

升级后的首个阻塞点是编译期类缺失。这类问题通常源于包路径迁移(Package Relocation)。全局替换旧的 import 语句即可,接口逻辑本身通常无需调整。

组件分类 旧路径(Spring Boot 3.x) 新路径(Spring Boot 4.x) 变更原因
异常处理 org.springframework.boot.web.servlet.error.ErrorController org.springframework.boot.webmvc.error.ErrorController 区分 Web MVC 与 WebFlux,按运行时模型重新归类。
缓存指标 org.springframework.boot.actuate.metrics.cache.CacheMeterBinderProvider org.springframework.boot.cache.metrics.CacheMeterBinderProvider 将缓存指标采集能力从 Actuator 解耦,下沉至 Cache 模块。

排查优先级:

  1. 确认目标类是否发生了包名迁移。
  2. 确认目标类是否被拆分至独立模块。
  3. 确认当前引用的 Starter 是否包含该模块。

自动配置拆分与显式依赖引入

在 Spring Boot 3 时代,诸多组件依赖通过 spring-boot-autoconfigure 隐式传递。Boot 4 将其进一步拆解,相关的自动配置被分散到核心模块中。

异常现象

  • 抛出 ClassNotFoundException
  • 无法解析 KafkaAutoConfiguration 等类。
  • 原有的组件功能失效。

根因与修复方案

必须显式引入对应领域的 Starter 以获取原有能力。

Kafka 依赖引入:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-kafka</artifactId>
</dependency>

Cache 依赖引入:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>

遵循架构规范,我选择优先使用官方 Starter 组合依赖,摒弃手工拼凑底层模块。在短期过渡中,引入 spring-boot-autoconfigure-classic 可作临时止血,但长期来看违背了 Boot 4 的设计初衷。

WebMvcConfigurer API 演进:转向 Builder 模式

在 Spring Framework 7.0 底层,直接操作 HttpMessageConverter 列表的模式被废弃,转为更严谨的 Builder 模式。

废弃的实现

1
2
3
4
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
// 隐式且不可控的修改 converters
}

新版构建契约

1
2
3
4
5
6
@Override
public void configureMessageConverters(HttpMessageConverters.ServerBuilder builder) {
// 明确的插入顺序控制
builder.addFirst(...);
builder.add(...);
}

此变更强制开发者以“声明式扩展”替代“集合篡改”,保证了转化器顺序的确定性。

避免全局 Jackson 序列化污染

在历史代码中,为避免客户端传递异常 Accept 头(如 text/html)导致 406 Not Acceptable,曾通过修改 MappingJackson2HttpMessageConverter 扩大其 supportedMediaTypes。这污染了 Jackson 的职责边界,为二进制流处理埋下了隐患。

我的重构策略是回归内容协商层进行根治:忽略客户端的不规范 Accept,默认统一返回 JSON。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer
.ignoreAcceptHeader(true)
.defaultContentType(MediaType.APPLICATION_JSON);
}
}

通过上述配置,内容协商决定输出类型,而 Jackson 仅专注 JSON 序列化。对于包含页面渲染或下载的混合型应用,我在特定的 @ExceptionHandler 中显式设置 MediaType.APPLICATION_JSON,从而收敛兜底范围。

Spring Cloud 2025.1 网关与客户端架构调整

Spring Cloud 2025.1 的核心在于“运行时模型分流”,彻底区分了 WebFlux 网关、MVC 网关以及服务端与客户端的消息转换器。

网关依赖重构:区分运行时模型

旧的 spring-cloud-starter-gateway 已被废弃,强行使用会导致 GlobalFilter 丢失与启动失败。必须依据运行时模型重新引入。

响应式 WebFlux 网关依赖:

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway-server-webflux</artifactId>
</dependency>

同步阻塞式 MVC 网关依赖:

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway-server-webmvc</artifactId>
</dependency>

在 MVC 模型下,需回归标准的 Servlet Filter,废弃响应式下的 GlobalFilter

路由与过滤器配置前缀迁移

即便依赖修复,若配置未更新,网关路由依然失效,根因是配置前缀的同步变更。

历史配置前缀

1
2
3
4
spring:
cloud:
gateway:
...

新版配置前缀

1
2
3
4
5
6
spring:
cloud:
gateway:
server:
webflux:
...

HttpMessageConverters 与客户端转换器定制

为 Feign 注入转换器时,路径已变更为 org.springframework.boot.http.converter.autoconfigure.HttpMessageConverters。官方倾向于将客户端与服务端转换器物理隔离。

在网关中配置 Feign 的 JSON 解析能力时,我采用了 ClientHttpMessageConvertersCustomizer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import org.springframework.boot.http.converter.autoconfigure.ClientHttpMessageConvertersCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FeignConverterConfig {

@Bean
public ClientHttpMessageConvertersCustomizer customClientConverters() {
return builder -> {
builder.withJsonConverter(HttpMessageConverterUtils.getMappingJackson2HttpMessageConverter());
};
}
}

面对下游服务返回错误 Content-Type(如 text/html 但实为 JSON)的情况,我没有扩大解码器的媒体类型支持,而是直接在 Feign 解码层进行拦截治理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import feign.Response;
import feign.codec.Decoder;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;

public class JsonForceDecoder implements Decoder {

private final Decoder delegate;

public JsonForceDecoder(Decoder delegate) {
this.delegate = delegate;
}

@Override
public Object decode(Response response, Type type) throws Exception {
Map<String, java.util.Collection<String>> headers = new HashMap<>(response.headers());
headers.put("Content-Type", java.util.Collections.singletonList("application/json"));

// 强制重写响应头为 application/json,交由标准解码器处理
Response modifiedResponse = response.toBuilder().headers(headers).build();
return delegate.decode(modifiedResponse, type);
}
}

此方案精准隔离了外部系统的不规范行为,同时保障了当前系统 Jackson 配置的纯净度。

Release Notes 关键特性评估

查阅官方 Release Notes,还有以下几项变动:

  1. 迁移缓冲策略: 官方建议跨度较大的项目先升级至 Spring Boot 3.5,以消化旧 API 废弃带来的冲击。
  2. 构建链路对齐: 新版支持 Gradle 9,不仅是业务代码的更新,构建系统(包括本地插件、Sonar 扫描等)亦需同步提基线。
  3. HTTP Service Clients 增强: 提供了自动配置支持,具备取代 RestTemplate 的潜力,成为更轻量的声明式 RPC 方案。
  4. API Versioning 标准化: spring.mvc.apiversion.* 提供了官方维度的路由版本控制抽象,利于后续淘汰自研拦截器。
  5. OpenTelemetry 原生接入: 引入 spring-boot-starter-opentelemetry,简化了链路追踪与指标导出的拼装工作。
  6. 基础设施监控解耦: SSL 健康检查逻辑调整,MongoDB 健康检查移出强依赖。此类变化需同步刷新运维层的监控告警阈值。

实践过程问题排查

JDK 版本与构建链协同

系统升级至 Java 21 时,需全局对齐本地 JAVA_HOME、Docker 基础镜像及 CI 环境的 JRE。同时,测试框架补齐了 junit-platform-launcher,SonarQube 扩展自 SonarQubeExtension 迁移至 SonarExtension

MVC 自动配置扩展点迁移

大量的自动配置扩展点被迁移至新目录:

1
2
3
4
5
// 旧路径
org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations

// 新路径
org.springframework.boot.webmvc.autoconfigure.WebMvcRegistrations

这警示我们在排查编译错误时,需重点检索 CacheAutoConfigurationKafkaProperties 等同样发生迁移的配置类。

OpenFeign 深度扩展适配

对于定制了 Feign Decoder 的场景,已无法基于 ObjectFactory<HttpMessageConverters> 注入。我将其彻底重构,切换至新的 FeignHttpMessageConverters 依赖路径,以适配 2025.1 的契约。

Actuator 与 Cache 机制变动

CacheAutoConfiguration 移至 org.springframework.boot.cache.autoconfigureNonUniqueCacheException 亦随之调整。涉及自定义缓存指标、二级缓存封装的代码均产生破坏性报错,需对应修改包依赖。

复盘与架构演进反思

本次底座升级深刻体现了 Spring 框架拒绝“模糊职责”与“隐式耦合”的设计决心。从 Web MVC 与 WebFlux 的严格分界,到客户端服务端配置的物理隔离,框架正通过 API 编译期报错倒逼项目结构收敛。

总结我的排障执行链路:

  1. 修复底层依赖与 Starter,严格对齐新版运行时模型。
  2. 批量替换包路径,解决由于模块拆解带来的编译阻断。
  3. 调整 application.yml 前缀,恢复配置读取链路。
  4. 依据 Builder 模式重塑 WebMvcConfigurer 及各类消息转换器扩展点。
  5. 清理历史反模式,移除针对 Jackson 的全局范围污染与运行时 Hack 逻辑。

此次升级不仅是版本号的跳跃,更是消除历史技术债、明确模块边界的最佳工程实践节点。后续演进中,我计划逐步引入官方 API Versioning 及 OpenTelemetry 规范,持续提升系统的可观测性与架构整洁度。

参考资料