Spring Cloud Function 中的函式式 Bean 註冊

工程 | Dave Syer | 2018 年 10 月 22 日 | ...

Spring Cloud Function 在 2.0 版本(仍處於里程碑階段)中有一些新特性,其中最顯著的可能是實現“完全函式式”的能力。這得益於 Spring Boot 2.1 和 Spring Framework 5.1 的變化,它意味著 Spring 應用中 Bean 定義的一種不同思考方式,同時也顯著提升了啟動效能。

AWS 成本節省

最好用一張圖來開頭,特別是它能講述一個故事時。這裡有一張圖,展示了 Spring Cloud Function 2.0 相較於 1.0 的改進,比較了 AWS 冷啟動的成本。

Memory Cost

x 軸是記憶體大小(MB),y 軸是冷啟動成本(GBsec)。最顯著的效果體現在低記憶體容器上,2.0 版本的成本幾乎是 1.0 版本的四分之一。“Custom”函式更快(比 Spring Cloud Function 1.0 快 10 倍)——它是一個使用 Spring Cloud Function 和函式式 Bean 的自定義 AWS 執行時。改進的根本原因在於啟動時間的顯著縮短,這又來源於在應用程式中使用了函式式的 Bean 定義形式。如果你需要入門介紹,Josh 很久以前做了一個關於函式式 Bean 註冊的影片(在 YouTube 上)。現在讓我們仔細看看它在 Spring Cloud Function 中是如何工作的。

函式式 Bean 定義與傳統 Bean 定義的比較

以下是 Spring Cloud Function 1.0 版本的一個應用程式示例,使用了熟悉的 `@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);
  }

}

你可以透過將其與所有依賴項一起打包成 jar 檔案並上傳到 Amazon 來在 AWS Lambda(例如)中執行它。該專案還支援 Azure FunctionsApache OpenWhisk。其他無伺服器提供商,例如 Oracle FnRiff,維護著自己的繫結。

你還可以透過在 classpath 中包含 `spring-cloud-function-starter-web` 來在應用程式自身的 HTTP 伺服器中執行上面的應用。執行 main 方法將暴露一個端點,你可以使用該端點來呼叫那個 `uppercase` 函式。

$ curl localhost:8080 -d foo
FOO

1.0 版本中的 Web 介面卡是使用 Spring MVC 實現的,因此你需要一個 Servlet 容器。在 Spring Cloud Function 2.0 中,你也可以使用 Webflux,並且預設伺服器是 Netty(儘管如果你願意,仍然可以使用 Servlet 容器)——只需包含 `spring-cloud-starter-function-webflux` 依賴項即可。功能是相同的,並且使用者應用程式程式碼可以在兩者中通用。

然而,在 2.0 版本中,使用者應用程式程式碼可以重寫為“函式式”形式,如下所示

@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(FunctionType.from(String.class).to(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` 中也是可行的。關鍵在於透過類宣告在執行時獲得泛型型別資訊。

如果你新增 `spring-cloud-starter-function-webflux` 依賴,應用程式就可以在其自身的 HTTP 伺服器中執行(目前與 MVC starter 不相容,因為嵌入式 Servlet 容器的函式式形式尚未實現)。該應用程式在 AWS Lambda 或 Azure Functions 中也能正常執行,並且啟動時間有了顯著改進(如上圖所示)。下圖展示了另一張圖中的啟動時間(y 軸為啟動時間,單位為秒)。

Memory Startup Time

測試函式式應用程式

Spring Cloud Function 2.0 還提供了一些用於整合測試的實用工具,這些工具對於 Spring Boot 使用者來說會非常熟悉。例如,這是一個用於測試上述應用程式 HTTP 伺服器包裝的整合測試示例。

@RunWith(SpringRunner.class)
@FunctionalSpringBootTest
@AutoConfigureWebTestClient
public class FunctionalTests {

	@Autowired
	private WebTestClient client;

	@Test
	public void words() throws Exception {
		client.post().uri("/").body(Mono.just("foo"), String.class)
                    .exchange().expectStatus().isOk()
                          .expectBody(String.class).isEqualTo("FOO");
	}

}

這個測試幾乎與你為同一個應用程式的 `@Bean` 版本編寫的測試完全相同——唯一的區別是使用了 `@FunctionalSpringBootTest` 註解,而不是常規的 `@SpringBootTest`。所有其他部分,比如 `@Autowired` 的 `WebTestClient`,都是標準的 Spring Boot 特性。

主流 Spring Boot 應用中的函式式 Bean 定義

Spring Boot 與函式式 Bean 註冊配合得很好——Spring Cloud Function 構建並執行在 Spring Boot 上——但 Spring Boot 中一些最有用的特性,即自動配置,都是以非函式式風格編寫的。與整個 Spring Boot 相比,大多數 Spring Cloud Function 應用的範圍相對較小,因此我們可以輕鬆地將其適應這些函式式 Bean 定義。如果你超出了這個有限的範圍,可以透過切換回 `@Bean` 風格的配置或採用混合方法來擴充套件你的 Spring Cloud Function 應用。將類似的功能擴充套件到 Spring Boot 生態系統的其餘部分還需要一些時間,但我們正在積極地開展這項工作。請嘗試使用 Spring Cloud Function 2.0,如果有時間,請向我們提供一些反饋——GA 版本即將釋出。

獲取 Spring 新聞通訊

訂閱 Spring 新聞通訊保持聯絡

訂閱

領先一步

VMware 提供培訓和認證,助你快速提升。

瞭解更多

獲取支援

Tanzu Spring 透過簡單的訂閱即可提供對 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位制檔案。

瞭解更多

即將舉行的活動

檢視 Spring 社群所有即將舉行的活動。

檢視全部