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 轴表示冷启动成本(GB 秒)。最显著的效果体现在低内存容器上,使用 2.0 版本时成本几乎降低了 4 倍。“Custom”函数甚至更快(相比 Spring Cloud Function 1.0 提升了 10 倍)—— 它是一个使用 Spring Cloud Function 和函数式 Bean 的自定义 AWS 运行时。这种改进的根源在于启动时间的显著缩短,而这又来自于应用中使用了函数式的 Bean 定义形式。Josh 曾发布过一个关于函数式 Bean 注册的视频,如果你需要入门介绍(可在 YouTube 上观看)。现在,让我们更深入地了解它在 Spring Cloud Function 中是如何工作的。

函数式与传统 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,维护着自己的绑定。

你也可以通过在类路径中包含 spring-cloud-function-starter-web 来在自己的 HTTP 服务器中运行上述应用。运行主方法将暴露一个端点,你可以用它来调用 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,否则部分信息会丢失。

使用 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 容器的函数式形式尚未实现)。该应用在 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。所有其他部分,例如 @AutowiredWebTestClient,都是标准的 Spring Boot 功能。

主流 Spring Boot 应用中的函数式 Bean 定义

Spring Boot 与函数式 Bean 注册完美兼容——Spring Cloud Function 是构建在 Spring Boot 之上的——但 Spring Boot 中一些最有用的功能,即自动配置,都是以非函数式风格编写的。大多数 Spring Cloud Function 应用的范围相对较小,与整个 Spring Boot 相比,因此我们能够轻松地将其适配到这些函数式 Bean 定义。如果你需要超出这个有限范围的功能,可以通过切换回 @Bean 风格配置,或使用混合方法来扩展你的 Spring Cloud Function 应用。将同样的功能扩展到 Spring Boot 生态系统的其他部分需要更长的时间,但我们正在积极地进行这项工作。请尝试使用 Spring Cloud Function 2.0,如果你有时间,请提供反馈——GA 版本即将发布。

获取 Spring 新闻通讯

通过 Spring 新闻通讯保持联系

订阅

领先一步

VMware 提供培训和认证,助您加速进步。

了解更多

获得支持

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件,只需一份简单的订阅。

了解更多

即将举行的活动

查看 Spring 社区所有即将举行的活动。

查看所有