函式式 Bean 定義
Spring Cloud Function 支援“函式式”風格的 Bean 宣告,適用於需要快速啟動的小型應用程式。函式式 Bean 宣告風格是 Spring Framework 5.0 的一個特性,並在 5.1 中得到了顯著增強。
函式式與傳統 Bean 定義的比較
這是一個普通的 Spring Cloud Function 應用程式,採用熟悉的 @Configuration 和 @Bean 宣告風格
@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 型別獨立類,並使用 run() 方法的替代形式將其註冊到 SpringApplication,它也將工作。主要的是透過類宣告在執行時可用的泛型型別資訊。
假設您有
@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>5.0.0</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");
}
}