Spring Cloud Function 中的 Functional 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 的成本幾乎降低了 4 倍。「Custom」函數甚至更快(比 Spring Cloud Function 1.0 快 10 倍) - 它是一個使用 Spring Cloud Function 和 functional bean 的 自訂 AWS runtime。改進的原因在於啟動時間顯著縮短,而這又來自於在應用程式中使用 functional 形式的 bean 定義。如果你需要介紹,Josh 之前做了一個關於 functional bean 註冊的 影片(在 YouTube 上)。現在讓我們仔細看看它在 Spring Cloud Function 中是如何運作的。

比較 Functional 與傳統 Bean 定義

這是一個來自 1.0 版本的 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);
  }

}

你可以將它與所有依賴項一起打包成 jar 檔案,並上傳到 Amazon,從而在 AWS Lambda 中運行它(例如)。該專案還支援 Azure FunctionsApache OpenWhisk。其他無伺服器供應商,例如 Oracle FnRiff,維護他們自己的綁定。

你也可以通過在 classpath 上包含 spring-cloud-function-starter-web,在自己的 HTTP 伺服器中運行上面的應用程式。運行 main 方法會暴露一個 endpoint,你可以使用它來 ping uppercase 函數。

$ curl localhost:8080 -d foo
FOO

1.0 中的 web adapter 是使用 Spring MVC 實現的,因此你需要一個 Servlet 容器。在 Spring Cloud Function 2.0 中,你也可以使用 Webflux,預設伺服器是 netty(即使你仍然可以使用 Servlet 容器,如果你想的話)- 只需包含 spring-cloud-starter-function-webflux 依賴項即可。功能相同,並且使用者應用程式程式碼可以在兩者中使用。

但在 2.0 中,使用者應用程式程式碼可以改寫成「functional」形式,如下所示

@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。這是一個 wrapper,其中包含函數以及有關輸入和輸出類型的資訊。在應用程式的 @Bean 形式中,該資訊可以通過反射推導出來,但在 functional 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。最重要的是,泛型類型資訊可以在執行時通過類別宣告獲得。

如果新增 spring-cloud-starter-function-webflux,則應用程式會在自己的 HTTP 伺服器中運行(目前無法與 MVC starter 一起使用,因為嵌入式 Servlet 容器的 functional 形式尚未實現)。該應用程式也可以在 AWS Lambda 或 Azure Functions 中正常運行,並且啟動時間的改進非常顯著(如上圖所示)。這是另一個圖表中的啟動時間(y 軸上的啟動時間,單位為秒)

Memory Startup Time

測試 Functional 應用程式

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 應用程式中的 Functional Bean 定義

Spring Boot 可以很好地與 functional bean 註冊一起使用 - Spring Cloud Function 建立並運行在 Spring Boot 上 - 但 Spring Boot 中一些最有用的功能,即自動配置,都是以非 functional 樣式編碼的。與整個 Spring Boot 相比,大多數 Spring Cloud Function 應用程式的範圍相對較小,因此我們能夠輕鬆地將其調整為這些 functional bean 定義。如果超出這個有限的範圍,你可以通過切換回 @Bean 樣式配置或使用混合方法來擴展你的 Spring Cloud Function 應用程式。將相同種類的功能擴展到 Spring Boot 生態系統的其餘部分需要更長的時間,但這是我們正在積極努力的事情。請試用 Spring Cloud Function 2.0 並在你有時間時向我們提供一些回饋 - GA 版本即將推出。

取得 Spring 電子報

訂閱 Spring 電子報,保持聯繫

訂閱

領先一步

VMware 提供培訓和認證,以加速您的進度。

了解更多

取得支援

Tanzu Spring 在一個簡單的訂閱中提供對 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位檔案。

了解更多

即將到來的活動

查看 Spring 社群中所有即將到來的活動。

查看全部