功能 Bean 定義
Spring Cloud Function 支援「功能性」樣式的 Bean 宣告,適用於需要快速啟動的小型應用程式。功能性樣式的 Bean 宣告是 Spring Framework 5.0 的一項功能,並在 5.1 中進行了重大改進。
功能性 Bean 定義與傳統 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 http://localhost: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.1.3</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");
}
}