1. 概述
2022年末,Spring Boot 3 和 Spring Framework 6 自其誕生以來,帶來了生態系統中最重大的變革。它們引入了 Java 17 基線、從 javax.* 到 jakarta.* 的遷移,以及對 GraalVM 原生鏡像的早期支持。
現在,2025年,下一代技術即將到來:Spring Boot 4 和 Spring Framework 7。
這兩個版本繼續推進現代化進程。它們採用了最新的 Java 語言特性,並提供了更緊密的 Jakarta EE 11 兼容性。它們還提高了開發人員的生產力,並提供健壯的應用程序支持。
在本文中,我們將探討這兩個版本的關鍵主題,並提供解釋和代碼示例,以展示開發人員可以期待什麼。
2. 基線升級
在深入瞭解功能之前,請注意新的基線。
由於目前行業內的廣泛採用,Java 17 仍然是最低要求。 建議使用 Java 21 和 Java 25 以充分利用新的 JVM 功能,例如虛擬線程。 您可以在 Spring Blog 上找到官方聲明:http://disq.us/p/30iomjg。
與 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 可觀測性。 Spring Boot 4 升級到 Micrometer 2 並集成 OpenTelemetry starter。 這使得追蹤、日誌和指標能夠無縫協同工作。
3.3. 改進的 SSL 健康報告
如果證書鏈包含即將到期證書,我們現在會在新的 expiringChains 條目中看到它們。 WILL_EXPIRE_SOON 狀態已移除。相反,即將到期證書被報告為 VALID。
這些更改使團隊更容易在生產環境中監控 SSL 證書有效性,而無需產生誤報。
3.4. 模塊化
Spring Boot 4 的首要里程碑之一是將其自身的代碼庫重構為更模塊化的結構。
在 Spring Boot 3 中,許多核心模塊(如自動配置、啓動器依賴項和構建工具)被打包到更大的 Artifact 中。雖然這種方式方便,但有時會使依賴項管理變得困難,導致類路徑掃描的開銷增加,並增加了原生鏡像的佔位符。
從 Spring Boot 4 開始,團隊開始將自動配置和支持代碼拆分為更小、更專注的模塊。這種內部模塊化意味着:
- 更快的構建和原生鏡像生成。GraalVM AOT 處理無需處理不必要的提示和元數據。
- 更清潔的依賴項管理。可選集成(如 Micrometer、OpenTelemetry 或特定持久化技術)位於單獨的模塊中,而不是打包在一起。
- Spring 團隊和貢獻者改進了可維護性。模塊更直接地映射到功能。
<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 Registrar 的示例(正如你在這篇文章中看到的,該 Bean Registrar 也在 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");
});
};
}
}<em>ChristmasJoyClient</em> 隨後可以像往常一樣注入到其他 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. 彈性回退註解
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 版本中,當我們想要自定義異步任務的執行時,我們可以將一個 TaskDecorator 註冊到 ThreadPoolTaskExecutor 中。 這允許我們例如將 SecurityContext 或 logging 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該改進消除了使用樣板複合裝飾器以及在異步代碼中組合多個橫跨關注點(cross-cutting concerns)的需求,從而簡化了操作。
4.6. 使用 JSpecify 進行空安全
空安全註解在 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 互操作性,從而降低了大型代碼庫中 NullPointerException 的風險。
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 方面。但這些在生產力、性能和可維護性方面的收益使過渡變得值得。