Spring Boot 4 和 Spring Framework 7 – 新增功能
1. 概述
2022 年末,Spring Boot 3 和 Spring Framework 6 的发布,为 Spring 生态系统带来了自诞生以来最重大的变革。它们引入了 Java 17 基线、从 javax.* 到 jakarta.* 的迁移,以及对 GraalVM 原生镜像的早期支持。
如今,在 2025 年,下一代 Spring Boot 4 和 Spring Framework 7 即将到来。
这两个版本延续了 Spring 的现代化进程。它们采用了最新的 Java 语言特性,并与 Jakarta EE 11 实现了更紧密的兼容。此外,它们还提高了开发人员的效率,并提供了开箱即用的弹性应用支持。
在本文中,我们将详细介绍这些版本的主要内容,并通过解释和代码示例,突出开发人员可以期待的功能。
2. 基线升级
在深入探讨新功能之前,需要注意的是新的基线。
由于目前 Java 17 在业界应用广泛,因此它仍然是最低要求。强烈建议使用 Java 21 和 Java 25,以便充分利用虚拟线程等新的 JVM 特性。你可以在 Spring 博客中找到官方声明。
随着 Spring Framework 7 的发布,Jakarta EE 11 已全面集成。这意味着我们将升级到 Servlet 6.1、JPA 3.2 和 Bean Validation 3.1。
在 Kotlin 方面,现在支持 2.2 及更高版本。这带来了更流畅的协程集成,并使响应式代码的编写更加自然。
3. Spring Boot 4
Spring Boot 的第四个主要版本带来了多项改进。它增强了性能、可观测性、可维护性和配置支持。这些变化进一步巩固了其作为现代云原生 Java 应用程序基础架构的地位。
3.1. 原生镜像改进
Spring Boot 4 继续大力推进对 GraalVM 原生镜像的支持。它与 GraalVM 24 完全兼容。提前 (AOT) 处理得到了增强,这意味着更快的构建时间和更少的启动内存占用。
例如,Spring Data 引入了 AOT 仓库,即 AOT 处理会将查询方法转换为源代码,并与应用程序一起编译。
3.2. 可观测性:Micrometer 2 和 OpenTelemetry
云原生应用依赖于良好的可观测性。Spring Boot 3 引入了 Spring Observability。Spring Boot 4 升级到 Micrometer 2 并集成了 OpenTelemetry starter。这使得跟踪、日志和指标能够无缝协作。
3.3. 改进的 SSL 健康报告
如果证书链包含即将过期的证书,我们现在会在新的 expiringChains 条目中看到它们。WILL_EXPIRE_SOON 状态已移除。取而代之的是,即将过期的证书会被报告为 VALID。
这些更改使团队能够更轻松地在生产环境中监控 SSL 证书的有效性,而不会出现误报。
3.4.模块化
Spring Boot 4 的首批里程碑之一就是将其自身的代码库重构为更模块化的结构。
在 Spring Boot 3 中,许多核心模块(例如自动配置、启动依赖项和构建工具)被打包在较大的构件中。虽然这样做很方便,但有时却增加了依赖项管理的难度,增加了类路径扫描的开销,并增大了原生镜像的体积。
从 Spring Boot 4 开始,团队开始将自动配置和支持代码拆分成更小、更专注的模块。这种内部模块化意味着:
- 更快的构建速度和原生镜像生成速度。GraalVM AOT 处理无需处理不必要的提示和元数据。
- 更清晰的依赖管理。可选集成(例如 Micrometer、OpenTelemetry 或特定的持久化技术)位于单独的模块中,而不是捆绑在一起。
- 提高了 Spring 团队和贡献者的可维护性。模块与功能之间的映射更加直接。
作为开发人员,我们可能不会直接在 pom.xml 或 build.gradle 文件中注意到这种变化。如果我们使用启动依赖项,则无需进行任何更改。例如,当我们在 Hibernate 中使用 JPA 时,只需将以下依赖项添加到 pom.xml 文件中:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>区别在于底层:JPA 自动配置、Hibernate 集成和验证设置现在都属于独立的模块,这使得框架在运行时或 AOT 编译期间处理配置时能够更加灵活地进行选择。
如果我们不使用 starter 依赖项,则需要注意这些更改。您可以在 Spring Boot GitHub Wiki 中找到有关新模块的详细信息。
3.5. 新的 @ConfigurationPropertiesSource 注解
另一个有助于更好地模块化的特性是名为 @ConfigurationPropertiesSource 的新注解。此注解不会改变运行时配置属性的绑定方式。相反,它在构建时为 spring-boot-configuration-processor 提供提示。
当处理器为 @ConfigurationProperties 类生成元数据时,它通常会从定义该类的同一模块中收集信息。然而,在模块化项目中,我们有时会依赖于位于不同模块中的嵌套类型或基类,这些模块的源代码在构建时不可用。在这种情况下,生成的元数据可能不完整——例如,属性描述或默认值可能缺失。
通过使用 @ConfigurationPropertiesSource 注解标记类,我们可以指示处理器为其生成完整的元数据,即使该类没有直接使用 @ConfigurationProperties 注解。实际上,这意味着我们在跨模块工作时不再需要担心元数据缺失的问题。处理器会自动处理这些问题。
4. Spring Framework 7
Spring Framework 7 融合了用户期待已久的诸多特性,并对测试、API 设计和核心基础架构进行了精心的改进。这些变化在使框架现代化的同时,也减少了日常开发中的样板代码。
4.1. 测试改进
Spring 在测试期间使用上下文缓存,以在测试性能和隔离性之间取得平衡。本文将详细介绍上下文缓存及其可能存在的问题和解决方案。
Spring Framework 7 引入了测试上下文暂停功能。此前,长时间运行的集成测试即使在空闲状态下也会消耗资源。现在,Spring 可以暂停和恢复存储在上下文缓存中的上下文,从而节省内存并加快大型测试套件的执行速度。这对于 JMS 监听器容器或计划任务等场景尤为重要。
此外,新增的 RestTestClient 使得 REST 端点的测试更加便捷,类似于 WebTestClient,但无需引入响应式基础架构:
这使得 REST 测试更接近 WebTestClient 的简洁性,同时又无需依赖响应式基础架构:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class HelloWorldApiIntegrationTest {
RestTestClient client;
@BeforeEach
void setUp(WebApplicationContext context) {
client = RestTestClient.bindToApplicationContext(context)
.build();
}
@Test
void shouldFetchHelloV1() {
client.get()
.uri("/api/v1/hello")
.exchange()
.expectStatus()
.isOk()
.expectHeader()
.contentTypeCompatibleWith(MediaType.TEXT_PLAIN)
.expectBody(String.class)
.consumeWith(message -> assertThat(message.getResponseBody()).containsIgnoringCase("hello"));
}
}4.2 API 版本控制
最受用户期待的新功能之一是完善的 API 版本控制。
过去,我们需要自行实现版本控制,例如使用 URL 路径约定(/v1/)、自定义标头或媒体类型。现在,框架提供了原生支持。我们可以指定版本属性,如本示例所示。
@RestController
@RequestMapping("/hello")
public class HelloWorldController {
@GetMapping(version = "1", produces = MediaType.TEXT_PLAIN_VALUE)
public String sayHelloV1() {
return "Hello World";
}
@GetMapping(version = "2", produces = MediaType.TEXT_PLAIN_VALUE)
public String sayHelloV2() {
return "Hi World";
}
}我们也可以在控制器层面指定版本:
@RestController
@RequestMapping(path = "/hello", version = "3")
public class HelloWorldV3Controller {
@GetMapping(produces = MediaType.TEXT_PLAIN_VALUE)
public String sayHello() {
return "Hey World";
}
}
接下来,我们需要配置映射策略,可以是以下几种之一:
- 基于路径的映射(例如
/api/v1/hello与/api/v2/hello) - 基于查询参数的映射(例如
/hello?version=1与/hello?version=2) - 基于请求头的映射(例如
X-API-Version: 1与X-API-Version: 2) - 基于媒体类型头的映射(例如
Accept: application/json; version=1与Accept: application/json; version=2)
以下配置使用基于路径的映射:
@Configuration
public class ApiConfig implements WebMvcConfigurer {
@Override
public void configureApiVersioning(ApiVersionConfigurer configurer) {
configurer.usePathSegment(1);
}
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.addPathPrefix("/api/v{version}", HandlerTypePredicate.forAnnotation(RestController.class));
}
}Spring 会自动解析版本。这使得 API 的演进更加轻松,而不会破坏现有客户端。
4.3. 使用 @HttpServiceClient 实现更智能的 HTTP 客户端
另一个值得注意的特性是声明式 HTTP 客户端支持。它借鉴了 Feign 的理念,但更加轻量级且完全集成。
在旧版本的 Spring 中,我们需要为 HttpInterface 创建一个代理。虽然也有更智能的解决方案,但需要单独构建。例如,在这个仓库中,我们可以找到一个使用自定义 @HttpClient 注解和自定义 Bean 注册器的示例(正如我们在本文中看到的,Spring Framework 7 也对其进行了改进)。
现在,我们有了使用 @HttpServiceClient 注解的内置解决方案。让我们来看一个例子:
@HttpServiceClient("christmasJoy")
public interface ChristmasJoyClient {
@GetExchange("/greetings?random")
String getRandomGreeting();
}
接下来,我们需要启用类路径扫描,并配置客户端所属的服务组:
@Configuration
@Import(HttpClientConfig.HelloWorldClientHttpServiceRegistrar.class)
public class HttpClientConfig {
static class HelloWorldClientHttpServiceRegistrar extends AbstractClientHttpServiceRegistrar {
@Override
protected void registerHttpServices(GroupRegistry registry, AnnotationMetadata metadata) {
findAndRegisterHttpServiceClients(registry, List.of("com.baeldung.spring.mvc"));
}
}
@Bean
RestClientHttpServiceGroupConfigurer christmasJoyServiceGroupConfigurer() String baseUrl) {
return groups -> {
groups.filterByName("christmasJoy")
.forEachClient((group, clientBuilder) -> {
clientBuilder.baseUrl("https://christmasjoy.dev/api");
});
};
}
}然后,ChristmasJoyClient 就可以像往常一样注入到其他 Spring 组件中了:
@RestController
@RequestMapping(path = "/hello", version = "4")
@RequiredArgsConstructor
public class HelloWorldV4Controller {
private final ChristmasJoyClient christmasJoy;
@GetMapping(produces = MediaType.TEXT_PLAIN_VALUE)
public String sayHello() {
return this.christmasJoy.getRandomGreeting();
}
}
4.4. 弹性(Resilience)注解
Spring Retry 已经存在多年,但一直感觉像是一个“附加组件”。在 Spring Framework 7 中,弹性机制已内置。我们可以使用 Spring 注解来注解 Spring 组件方法,从而直接添加重试逻辑或并发限制:
@HttpServiceClient("christmasJoy")
public interface ChristmasJoyClient {
@GetExchange("/greetings?random")
@Retryable(maxAttempts = 3, delay = 100, multiplier = 2, maxDelay = 1000)
@ConcurrencyLimit(3)
String getRandomGreeting();
}
默认情况下,这些注解会被忽略,除非我们在某个配置中添加了 @EnableResilientMethods 注解。
这大大简化了添加弹性模式的过程,无需像 Resilience4j 这样的额外库,尽管它们仍然可以很好地集成。
这使得在运行时验证弹性策略变得更加容易,确保我们的注解确实被应用。
4.5. 多个 TaskDecorator Bean
在早期的 Spring 版本中,当我们想要自定义异步任务的执行时,我们可以在 ThreadPoolTaskExecutor 上注册一个 TaskDecorator。这允许我们将 SecurityContext 或日志 MDC 传播到异步线程中。但是,如果我们需要应用多个关注点,则必须手动创建一个复合装饰器。
从 Spring Framework 7 开始,我们现在可以在应用程序上下文中声明多个 TaskDecorator Bean。Spring 会自动将它们组合成一个链。每个装饰器会按照其 Bean 定义或 @Order 注解的顺序依次应用。
例如,我们有一个异步事件监听器:
@Component
@Slf4j
public class HelloWorldEventLogger {
@Async
@EventListener
void logHelloWorldEvent(HelloWorldEvent event) {
log.info("Hello World Event: {}", event.message());
}
}
当我们需要简单的日志记录和时间戳测量时,我们可以简单地注册两个 TaskDecorator bean:
@Configuration
@Slf4j
public class TaskDecoratorConfiguration {
@Bean
@Order(2)
TaskDecorator loggingTaskConfigurator() {
return runnable -> () -> {
log.info("Running Task: {}", runnable);
try {
runnable.run();
} finally {
log.info("Finished Task: {}", runnable);
}
};
}
@Bean
@Order(1)
TaskDecorator measuringTaskConfigurator() {
return runnable -> () -> {
final var ts1 = System.currentTimeMillis();
try {
runnable.run();
} finally {
final var ts2 = System.currentTimeMillis();
log.info("Finished within {}ms (Task: {})", ts2 - ts1, runnable);
}
};
}
}
生成的日志输出如下:
Running Task: com.baeldung.spring.mvc.TaskDecoratorConfiguration$$Lambda/0x00000ff0014325f8@57e8609
Hello World Event: "Happy Christmas"
Finished within 0ms (Task: java.util.concurrent.FutureTask@bb978d6[Completed normally])
Finished Task: com.baeldung.spring.mvc.TaskDecoratorConfiguration$$Lambda/0x00000ff0014325f8@57e8609这项改进消除了对样板复合装饰器的需要,并简化了在异步代码中组合多个横切关注点的过程。
4.6. 使用 JSpecify 实现 Null 安全
Java 生态系统中一直存在着各种各样的空值注解(例如 @Nonnull、@Nullable、@NotNull 等)。在 Spring Framework 7 中,团队采用了 JSpecify 作为标准:
@Configuration
public class ApiConfig implements WebMvcConfigurer {
@Override
public void configureApiVersioning(@NonNull ApiVersionConfigurer configurer) {
configurer.usePathSegment(1);
}
}这改进了 IDE 工具和 Kotlin 互操作性,降低了大型代码库中出现空指针异常的风险。
5. 弃用和移除
现代化带来了清理工作:
- javax.* 包已移除——仅支持 Jakarta EE 11。
- Jackson 2.x 的支持已停止;Spring 7 需要 Jackson 3.x。
- Spring JCL(日志桥接器)已被移除,取而代之的是 Apache Commons Logging。
- JUnit 4 的支持已完全移除——Spring Boot 4 和 Spring Framework 7 现在仅支持 JUnit Jupiter 6。
如果我们仍然依赖这些旧版 API,则应将迁移纳入升级计划。
6. 结论
Spring Boot 4 和 Spring Framework 7 不仅仅是增量版本。它们是 Java 开发迈向现代化、模块化、云原生时代的重大一步:
- API 版本控制和弹性注解使应用程序更容易演进和加固。
- JSpecify 的空安全机制和 Kotlin 支持可减少运行时错误。
- 声明式 HTTP 客户端简化了服务间的调用。
- 原生镜像支持和可观测性工具提升了云就绪度。
与所有重大升级一样,关键在于尽早开始测试应用程序,尤其是在依赖项升级和已弃用 API 方面。但生产力、性能和可维护性方面的优势使此次升级物有所值。