領先一步
VMware 提供培訓和認證,助您加速進步。
瞭解更多Spring Framework 6.2 的釋出說明詳細介紹了所有新功能。我不會在這裡贅述,但以下是一些吸引我眼球的功能:
MvcTester 以及大大改進的測試中的模擬 bean,提供了改進的測試支援。@Fallback bean 的概念,它本質上是 @Primary bean 的映象。@ExceptionHandler 以支援內容協商。@Reflective 和新的 @ReflectiveScan 註解,可以更輕鬆地反射非 Spring 管理的 bean。我熱切期待 @ReflectiveScan、片段渲染、改進的測試支援以及 @Fallback。讓我們來看看其中的一些實際應用!
@Fallback bean例如,您有兩個 Foo 型別的 bean,並想將它們注入到某個地方。如果您知道其中一個 bean 比另一個更受偏好,您可以指定該 bean 是 @Primary bean,Spring 將會從兩個(或三個,或任意數量)備選 bean 中選擇它,只要只有一個 @Primary bean。但如何做相反的事情呢?如何告訴 Spring 僅在沒有其他選擇時才選擇某個 bean?您可能會問,為什麼會有動態數量的 bean?假設 bean 僅在配置檔案啟用時或透過 @Conditional 測試可用。您可以將一個 bean 指定為*備用* bean;如果 Spring 沒有更好的選擇——沒有標記為 @Fallback 或標記為 @Primary 的 bean——那麼它將選擇*備用* bean。
@Fallback 演算法會影響*注入*時的演算法。因此,如果您有多個 Foo,但只想注入一個,您需要使用 @Primary 或新的 @Fallback。但這不會改變 ApplicationContext 中有多少 bean 可用。如果您注入所有 Foo 例項(如使用 Foo[] foos 或 Map<String,Foo> beansByNameAndInstance),那麼這將反映所有例項,包括那些被標記為 @Primary、@Fallback 等的例項。
一個例子
package com.example.bootiful_34.framework;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Fallback;
import org.springframework.context.annotation.Primary;
@Configuration
class FallbackDemoConfiguration {
@Bean
@Fallback
DefaultNoOpMessageProvider defaultNoOpFoo() {
return new DefaultNoOpMessageProvider();
}
@Bean
SimpleMessageProvider foo() {
return new SimpleMessageProvider();
}
@Bean
@Primary
SophisticatedMessageProvider sophisticatedFoo() {
return new SophisticatedMessageProvider();
}
@Bean
ApplicationRunner fallbackDemoConfigurationRunner(MessageProvider messageProvider) {
return args -> System.out.println(messageProvider.message());
}
}
class DefaultNoOpMessageProvider implements MessageProvider {
@Override
public String message() {
return "default noop implementation";
}
}
class SimpleMessageProvider implements MessageProvider {
@Override
public String message() {
return "simple implementation";
}
}
class SophisticatedMessageProvider implements MessageProvider {
@Override
public String message() {
return "\uD83E\uDD14 + \uD83C\uDFA9";
}
}
在此示例中,有三個 MessageProvider 型別的 bean,Spring 需要在它們之間進行區分,以便只選擇一個注入到 ApplicationRunner。在這種情況下,Spring 將選擇如上定義的 SophisticatedMessageProvider。註釋掉 SophisticatedMessageProvider bean 定義,並將 @Profile("foo") 新增到 SimpleMessageProvider,Spring 將選擇 DefaultNoOpMessageProvider 例項。取消註釋 SimpleMessageProvider,Spring 會立即再次選擇它。不錯。
測試有時需要在 Spring Environment 抽象中具有不同的屬性來改變支援測試的行為。因此,Spring 長期以來一直提供一種機制——用 @DynamicPropertySource 標註的靜態方法——透過這種機制,您可以在 Spring 的測試支援啟動您的 bean 配置之前向 Spring Environment 貢獻內容,以便在您的測試期間正確配置它。這是一個示例。
package com.example.bootiful_34.testing;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import static com.example.bootiful_34.testing.Messages.ONE;
import static com.example.bootiful_34.testing.Messages.TWO;
import static org.junit.jupiter.api.Assertions.assertEquals;
@SpringBootTest
@Import(PropertiesConfiguration.class)
class PropertyTest {
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("dynamic.message.one", () -> ONE);
}
@Test
void properties(@Autowired ApplicationContext ac) {
var environment = ac.getEnvironment();
assertEquals(ONE, environment.getProperty("dynamic.message.one"));
assertEquals(TWO, environment.getProperty("dynamic.message.two"));
}
}
在此類中,我們為 dynamic.message.one 貢獻了值,並將其指向 Messages 中定義的某些靜態變數。
package com.example.bootiful_34.testing;
class Messages {
static final String ONE = "this is a first message";
static final String TWO = "this is a second message";
}
但是 dynamic.message.two 呢?它是使用*新*功能定義的。
package com.example.bootiful_34.testing;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.test.context.DynamicPropertyRegistrar;
import org.springframework.test.context.DynamicPropertyRegistry;
import static com.example.bootiful_34.testing.Messages.TWO;
@TestConfiguration
class PropertiesConfiguration {
@Bean
SimplePropertyRegistrar simplePropertyRegistrar() {
return new SimplePropertyRegistrar();
}
static class SimplePropertyRegistrar implements DynamicPropertyRegistrar {
@Override
public void accept(DynamicPropertyRegistry registry) {
registry.add("dynamic.message.two", () -> TWO);
}
}
}
這不是很方便嗎?測試上下文中註冊的任何實現 DynamicPropertyRegistrar 的 bean 都可以向測試上下文的 Environment 貢獻值。簡單而優雅。
MockMvc 測試和更巧妙的 bean 替換我喜歡 Spring 的 MockMvc 類,它允許我輕鬆地測試——而且是用一種流暢的 DSL——給定的 Spring MVC HTTP 端點。然而,它一直有點令人沮喪的是,它感覺不像 AssertJ,或者不能與 AssertJ 一起工作,而且它的 DSL 也沒有那麼流暢。這些測試感覺像是 AssertJ 測試海洋中的孤島。但現在這種情況已經改變了。隆重推出 MockMvcTester!
package com.example.bootiful_34.testing;
import com.example.bootiful_34.framework.MessageProvider;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.test.context.DynamicPropertyRegistrar;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.test.context.bean.override.convention.TestBean;
import org.springframework.test.web.servlet.assertj.MockMvcTester;
import org.springframework.web.context.WebApplicationContext;
@SpringBootTest
@SuppressWarnings("unused")
class GreetingsControllerTest {
private static final String TEST_MESSAGE = "this is a first message";
private final MockMvcTester mockMvc;
@TestBean
private MessageProvider messageProvider;
GreetingsControllerTest(@Autowired WebApplicationContext wac) {
this.mockMvc = MockMvcTester.from(wac);
}
static MessageProvider messageProvider() {
return () -> TEST_MESSAGE;
}
@Test
void message() throws Exception {
var mvcTestResult = this.mockMvc.get().uri("/hello").accept(MediaType.APPLICATION_JSON).exchange();
Assertions.assertThat(mvcTestResult.getResponse().getContentAsString()).contains(TEST_MESSAGE);
}
}
您可以正常建立它,方法是例項化並直接傳入一個控制器例項,或者使用 ApplicationContext 進行初始化。在此示例中,我們採用了後一種方法。
為了使此示例更簡潔,我還使用了三個用於替換 bean 的新註解中的一個——@TestBean。順便說一下,這些註解在 Spring Framework 中,而不再是 Spring Boot 獨有的!@TestBean 告訴 Spring 您打算用您指定的另一個 bean 替換給定的 bean。它透過呼叫同一類中用 @TestBean 標註的欄位同名的 `static` 方法來派生該例項。