函式式 Bean 定義
Spring Cloud Function 支援一種“函式式”風格的 bean 宣告,適用於需要快速啟動的小型應用。函式式 bean 宣告風格是 Spring Framework 5.0 的一項特性,並在 5.1 版本中進行了顯著增強。
比較函式式與傳統 Bean 定義
這是一個使用熟悉的 @Configuration
和 @Bean
宣告風格的普通 Spring Cloud Function 應用
@SpringBootApplication
public class DemoApplication {
@Bean
public Function<String, String> uppercase() {
return value -> value.toUpperCase();
}
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
現在是函式式 bean:使用者應用程式碼可以重寫成“函式式”形式,如下所示
@SpringBootConfiguration
public class DemoApplication implements ApplicationContextInitializer<GenericApplicationContext> {
public static void main(String[] args) {
FunctionalSpringApplication.run(DemoApplication.class, args);
}
public Function<String, String> uppercase() {
return value -> value.toUpperCase();
}
@Override
public void initialize(GenericApplicationContext context) {
context.registerBean("demo", FunctionRegistration.class,
() -> new FunctionRegistration<>(uppercase())
.type(FunctionTypeUtils.functionType(String.class, String.class)));
}
}
主要區別在於
-
主類是一個
ApplicationContextInitializer
。 -
@Bean
方法已轉換為呼叫context.registerBean()
-
@SpringBootApplication
已替換為@SpringBootConfiguration
,以表示我們沒有啟用 Spring Boot 自動配置,但仍將該類標記為“入口點”。 -
Spring Boot 的
SpringApplication
已替換為 Spring Cloud Function 的FunctionalSpringApplication
(它是其子類)。
在 Spring Cloud Function 應用中註冊的業務邏輯 bean 型別是 FunctionRegistration
。這是一個包含函式及其輸入和輸出型別資訊的包裝器。在 @Bean
形式的應用中,這些資訊可以透過反射推匯出來,但在函式式 bean 註冊中,如果不使用 FunctionRegistration
,部分資訊會丟失。
除了使用 ApplicationContextInitializer
和 FunctionRegistration
外,另一種方法是讓應用程式本身實現 Function
(或 Consumer
或 Supplier
)。例如(等同於上面)
@SpringBootConfiguration
public class DemoApplication implements Function<String, String> {
public static void main(String[] args) {
FunctionalSpringApplication.run(DemoApplication.class, args);
}
@Override
public String apply(String value) {
return value.toUpperCase();
}
}
如果新增一個獨立的 Function
型別類,並使用 SpringApplication
的另一種 run()
方法形式註冊它,這也將奏效。關鍵是泛型型別資訊必須在執行時透過類宣告可用。
假設你有
@Component
public class CustomFunction implements Function<Flux<Foo>, Flux<Bar>> {
@Override
public Flux<Bar> apply(Flux<Foo> flux) {
return flux.map(foo -> new Bar("This is a Bar object from Foo value: " + foo.getValue()));
}
}
你將其註冊為
@Override
public void initialize(GenericApplicationContext context) {
context.registerBean("function", FunctionRegistration.class,
() -> new FunctionRegistration<>(new CustomFunction()).type(CustomFunction.class));
}
函式式 Bean 宣告的侷限性
大多數 Spring Cloud Function 應用的範圍相對較小,與整個 Spring Boot 相比,因此我們可以輕鬆地將其適配到這些函式式 bean 定義。如果超出這個有限範圍,可以透過切換回 @Bean
風格的配置,或採用混合方法來擴充套件 Spring Cloud Function 應用。例如,如果想利用 Spring Boot 自動配置與外部資料儲存整合,則需要使用 @EnableAutoConfiguration
。如果願意,你的函式仍然可以使用函式式宣告定義(即“混合”風格),但在這種情況下,需要透過設定 spring.functional.enabled=false
顯式關閉“完全函式式模式”,以便 Spring Boot 可以重新獲得控制權。
函式視覺化與控制
Spring Cloud Function 透過 Actuator 端點和程式設計方式支援對 FunctionCatalog
中可用函式的視覺化。
程式設計方式
要以程式設計方式檢視應用上下文中可用的函式,只需要訪問 FunctionCatalog
。在那裡可以找到獲取目錄大小、查詢函式以及列出所有可用函式名稱的方法。
例如,
FunctionCatalog functionCatalog = context.getBean(FunctionCatalog.class);
int size = functionCatalog.size(); // will tell you how many functions available in catalog
Set<String> names = functionCatalog.getNames(null); will list the names of all the Function, Suppliers and Consumers available in catalog
. . .
Actuator
由於 actuator 和 web 是可選的,必須首先手動新增其中一個 web 依賴以及 actuator 依賴。以下示例展示瞭如何新增 Web 框架的依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
以下示例展示瞭如何新增 WebFlux 框架的依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
可以按如下方式新增 Actuator 依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
還必須透過設定以下屬性來啟用 functions
actuator 端點:--management.endpoints.web.exposure.include=functions
。
訪問以下 URL 檢視 FunctionCatalog 中的函式:<host>:<port>/actuator/functions
例如,
curl https://:8080/actuator/functions
輸出應如下所示
{"charCounter":
{"type":"FUNCTION","input-type":"string","output-type":"integer"},
"logger":
{"type":"CONSUMER","input-type":"string"},
"functionRouter":
{"type":"FUNCTION","input-type":"object","output-type":"object"},
"words":
{"type":"SUPPLIER","output-type":"string"}. . .
測試函式式應用
Spring Cloud Function 還提供了一些整合測試工具,Spring Boot 使用者會非常熟悉。
假設這是你的應用程式
@SpringBootApplication
public class SampleFunctionApplication {
public static void main(String[] args) {
SpringApplication.run(SampleFunctionApplication.class, args);
}
@Bean
public Function<String, String> uppercase() {
return v -> v.toUpperCase();
}
}
這是一個包裝此應用程式的 HTTP 伺服器的整合測試
@SpringBootTest(classes = SampleFunctionApplication.class,
webEnvironment = WebEnvironment.RANDOM_PORT)
public class WebFunctionTests {
@Autowired
private TestRestTemplate rest;
@Test
public void test() throws Exception {
ResponseEntity<String> result = this.rest.exchange(
RequestEntity.post(new URI("/uppercase")).body("hello"), String.class);
System.out.println(result.getBody());
}
}
或者當使用函式 bean 定義風格時
@FunctionalSpringBootTest
public class WebFunctionTests {
@Autowired
private TestRestTemplate rest;
@Test
public void test() throws Exception {
ResponseEntity<String> result = this.rest.exchange(
RequestEntity.post(new URI("/uppercase")).body("hello"), String.class);
System.out.println(result.getBody());
}
}
這個測試幾乎與為同一應用的 @Bean
版本編寫的測試相同 - 唯一的區別是使用了 @FunctionalSpringBootTest
註解,而不是常規的 @SpringBootTest
。所有其他部分,如 @Autowired
TestRestTemplate
,都是標準的 Spring Boot 特性。
為了幫助正確新增依賴,這裡是 POM 中的摘錄
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<relativePath/> <!-- lookup parent from repository -->
</parent>
. . . .
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-web</artifactId>
<version>4.2.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
或者,可以只使用 FunctionCatalog
為非 HTTP 應用編寫測試。例如
@FunctionalSpringBootTest
public class FunctionalTests {
@Autowired
private FunctionCatalog catalog;
@Test
public void words() {
Function<String, String> function = catalog.lookup(Function.class,
"uppercase");
assertThat(function.apply("hello")).isEqualTo("HELLO");
}
}