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 社区所有即将举行的活动。

查看全部