功能 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,否則某些資訊會遺失。

使用 ApplicationContextInitializerFunctionRegistration 的替代方案是讓應用程式本身實作 Function(或 ConsumerSupplier)。範例(與上述等效)

@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");
	}

}