编程

Spring Boot 4 和 Spring Framework 7 – 新增功能

2 2025-12-11 03:13:00

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.xmlbuild.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: 1X-API-Version: 2
  • 基于媒体类型头的映射(例如 Accept: application/json; version=1Accept: 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 版本中,当我们想要自定义异步任务的执行时,我们可以在 ThreadPoolTask​​Executor 上注册一个 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 方面。但生产力、性能和可维护性方面的优势使此次升级物有所值。

下一篇