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 版本的成本几乎降低了 4 倍。“自定义”函数甚至更快(比 Spring Cloud Function 1.0 快 10 倍)——它是一个使用具有函数式 Bean 的 Spring Cloud Function 的自定义 AWS 运行时。改进的根本原因在于启动时间的大幅缩短,而这反过来又源于在应用程序中使用函数式 Bean 定义形式。如果您需要入门介绍,Josh 之前制作了一个关于函数式 Bean 注册的视频(在 YouTube 上)。现在让我们更仔细地看看它在 Spring Cloud Function 中是如何工作的。

比较函数式 Bean 定义与传统 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);
  }

}

您可以通过将其与所有依赖项一起打包并上传到 Amazon 来在 AWS Lambda(例如)中运行它。该项目还支持Azure FunctionsApache OpenWhisk。其他无服务器提供商,例如Oracle FnRiff,维护自己的绑定。

您也可以仅通过在类路径上包含spring-cloud-function-starter-web 来在它自己的 HTTP 服务器中运行上面的应用程序。运行 main 方法将公开一个可用于 ping 该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 启动器,因为嵌入式 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 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 社区中所有即将举行的活动。

查看全部