領先一步
VMware 提供培訓和認證,助您加速前進。
瞭解更多Spring Framework 6.2 釋出說明提供了對所有新功能的更詳細介紹。我不會在這裡重複所有內容,但以下是一些引起我注意的功能
MvcTester
以及測試中大幅改進的模擬 Bean。@Fallback
Bean 的概念,這本質上是 @Primary
Bean 的映象。我一直熱切期待 @ReflectiveScan
、片段渲染、改進的測試支援以及 @Fallback
。讓我們看看其中一些實際應用的例子!
@Fallback
Bean假設您有兩個型別為 Foo
的 Bean,並希望將它們注入到某個地方。如果您知道您更喜歡其中一個 Bean 被注入,則可以將該 Bean 指定為 @Primary
Bean,只要只有一個 @Primary
Bean,Spring 就會從兩個(或三個,或任意數量)備選項中選擇它。但是,如何做相反的操作呢?您如何告訴 Spring 僅在沒有其他可用的情況下選擇某個 Bean?您可能會問,為什麼 Bean 的數量會是動態的?假設您有一些 Bean 僅在啟用 Profile 或透過 @Conditional
測試時才可用。您可以將一個 Bean 指定為備用 (fallback) Bean;如果 Spring 沒有更好的選擇——沒有標記 @Fallback
或標記為 @Primary
的 Bean——那麼它將選擇該備用 (fallback) Bean。
@Fallback
演算法影響的是注入時的演算法。因此,如果您有多個 Foo
Bean,但只想注入其中一個,則需要使用 @Primary
或新的 @Fallback
。但這不會改變 ApplicationContext
中可用的 Bean 數量。如果您注入 Foo
的所有例項(例如使用 Foo[] foos
或 Map<String,Foo> beansByNameAndInstance
),那麼這將反映所有例項,包括標記有 @Primary
、@Fallback
等的 Bean。
一個示例
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 定義,並在 SimpleMessageProvider
上新增 @Profile("foo")
,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
類,它讓我能夠輕鬆地測試給定的 Spring MVC HTTP 端點——而且使用了流暢的 DSL。然而,它總是有點麻煩,因為它感覺不像 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);
}
}
您通常可以直接例項化並傳入一個 Controller 例項來建立它,或者使用 ApplicationContext
來初始化它。在這個示例中,我們採用了後一種方法。
為了讓這個示例更清晰,我還使用了三個用於替換 Bean 的新註解之一——@TestBean
。順便說一下,這些註解現在位於 Spring Framework 中,不再是 Spring Boot 獨有!@TestBean
告訴 Spring 您打算用另一個符合您規範的 Bean 來替換給定的 Bean。它透過呼叫一個與同一類中用 @TestBean
註解的欄位同名的 static
方法來派生該例項。