131. 功能 Bean 定义

对于需要快速启动的小型应用程序,Spring Cloud Function 支持“声明式” bean 声明样式。 bean 声明的功能样式是 Spring Framework 5.0 的功能,在 5.1 中进行了重大增强。

131.1 将功能与传统 Bean 定义进行比较

这是一个带有熟悉的@Configuration@Bean声明样式的香草 Spring Cloud Function 应用程序:

@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);
  }

}

您可以在无服务器平台(如 AWS Lambda 或 Azure Functions)中运行上述命令,也可以仅在 Classpath 中包含spring-cloud-function-starter-web来在自己的 HTTP 服务器中运行上述命令。运行 main 方法将公开一个端点,您可以使用该端点 ping uppercase函数:

$ curl localhost:8080 -d foo
FOO

spring-cloud-function-starter-web中的 Web 适配器使用 Spring MVC,因此您需要一个 Servlet 容器。您也可以在默认服务器为 netty 的地方使用 Webflux(即使您仍然愿意使用 Servlet 容器也可以)-只需包含spring-cloud-starter-function-webflux依赖项即可。功能相同,并且两者都可以使用用户应用程序代码。

现在介绍功能性 bean:可以将用户应用程序代码重铸为“功能性”形式,如下所示:

@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。这是一个包装,其中包含函数以及有关 Importing 和输出类型的信息。在应用程序的@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 uppercase(String value) {
    return value.toUpperCase();
  }

}

如果您添加单独的独立类型Function的类并使用run()方法的替代形式向SpringApplication注册,它也将起作用。最主要的是,泛型类型信息可在运行时通过类声明获得。

如果您添加spring-cloud-starter-function-webflux,则该应用程序将在其自己的 HTTP 服务器上运行(由于尚未实现嵌入式 Servlet 容器的功能形式,因此该应用程序目前无法与 MVC 启动器一起使用)。该应用程序还可以在 AWS Lambda 或 Azure Functions 中正常运行,并且启动时间的改善是惊人的。

Note

“精简型” Web 服务器对Function签名的范围有一些限制-特别是它(目前)还不支持MessageImporting 和输出,但是 POJO 和任何类型的Publisher应该可以。

131.2 测试功能应用程序

Spring Cloud Function 还具有一些用于集成测试的 Util,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版本编写的测试相同-唯一的区别是@FunctionalSpringBootTestComments,而不是常规@SpringBootTest。其他所有部件(如@Autowired WebTestClient)都是标准的 Spring Boot 功能。

或者,您可以只使用FunctionCatalog为非 HTTP 应用程序编写测试。例如:

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

	@Autowired
	private FunctionCatalog catalog;

	@Test
	public void words() throws Exception {
		Function<Flux<String>, Flux<String>> function = catalog.lookup(Function.class,
				"function");
		assertThat(function.apply(Flux.just("foo")).blockFirst()).isEqualTo("FOO");
	}

}

(即使用户使用更简单的签名声明FunctionCatalog,它们也总是从Flux返回到Flux的函数.)

131.3 功能 Bean 声明的局限性

与整个 Spring Boot 相比,大多数 Spring Cloud Function 应用程序的范围相对较小,因此我们能够轻松地使其适应这些功能 Bean 定义。如果您超出了有限的范围,则可以通过切换回@Bean样式配置或使用混合方法来扩展 Spring Cloud Function 应用。例如,如果您想利用 Spring Boot 自动配置来与外部数据存储区集成,则需要使用@EnableAutoConfiguration。如果需要,仍可以使用函数声明来定义函数(即“混合”样式),但是在这种情况下,您将需要使用spring.functional.enabled=false明确关闭“全功能模式”,以便 Spring Boot 可以收回控制权。