Functional Bean Definitions

Spring Cloud Function 支持小应用程序的功能样式 bean 声明,这些应用程序需要快速启动。功能样式的 bean 声明是 Spring Framework 5.0 的一项功能,在 5.1 中得到显著增强。

Comparing Functional with Traditional Bean Definitions

下面是一个来自熟悉的 @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);
  }

}

现在,对于功能 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(FunctionTypeUtils.functionType(String.class, String.class)));
  }

}

主要差异如下:

  • 主类是 ApplicationContextInitializer

  • @Bean 方法已转换为 context.registerBean() 调用。

  • @SpringBootApplication 已替换为 @SpringBootConfiguration,表示我们未启用 SpringBoot 自动配置,但仍将此类标记为“入口点”。

  • 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 注册它,也可以工作。最重要的是,泛型类型信息在运行时通过类声明提供。

假设您有

@Component
public class CustomFunction implements Function<Flux<Foo>, Flux<Bar>> {
	@Override
	public Flux<Bar> apply(Flux<Foo> flux) {
		return flux.map(foo -> new Bar("This is a Bar object from Foo value: " + foo.getValue()));
	}

}

您已将其注册为:

@Override
public void initialize(GenericApplicationContext context) {
		context.registerBean("function", FunctionRegistration.class,
				() -> new FunctionRegistration<>(new CustomFunction()).type(CustomFunction.class));
}

Limitations of Functional Bean Declaration

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

Function visualization and control

Spring Cloud Function 支持通过 Actuator 端点以及编程方式可视化 FunctionCatalog 中可用的函数。

Programmatic way

要通过编程方式查看应用程序上下文中可用的函数,您只需要访问 FunctionCatalog。在那里,您可以找到获取编目大小、查找函数以及列出所有可用函数名称的方法。

例如,

FunctionCatalog functionCatalog = context.getBean(FunctionCatalog.class);
int size = functionCatalog.size(); // will tell you how many functions available in catalog
Set<String> names = functionCatalog.getNames(null); will list the names of all the Function, Suppliers and Consumers available in catalog
. . .

Actuator

由于 Actuator 和 Web 是可选的,因此您必须首先添加一个 Web 依赖项以及手动添加 Actuator 依赖项。以下示例展示了如何添加 Web 框架的依赖项:

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-web</artifactId>
</dependency>

以下示例展示了如何添加 WebFlux 框架的依赖项:

<dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

您可以按如下方式添加 Actuator 依赖项:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

您还必须通过设置以下属性来启用 functions Actuator 端点:--management.endpoints.web.exposure.include=functions

访问以下 URL 以查看 FunctionCatalog 中的函数:http://<host>:<port>/actuator/functions

例如,

curl http://localhost:8080/actuator/functions

您的输出应如下所示:

{"charCounter":
	{"type":"FUNCTION","input-type":"string","output-type":"integer"},
 "logger":
 	{"type":"CONSUMER","input-type":"string"},
 "functionRouter":
 	{"type":"FUNCTION","input-type":"object","output-type":"object"},
 "words":
 	{"type":"SUPPLIER","output-type":"string"}. . .

Testing Functional Applications

Spring Cloud Function 还有一些对 Spring Boot 用户来说非常熟悉的用于集成测试的实用工具。

假设这是您的应用程序:

@SpringBootApplication
public class SampleFunctionApplication {

    public static void main(String[] args) {
        SpringApplication.run(SampleFunctionApplication.class, args);
    }

    @Bean
    public Function<String, String> uppercase() {
        return v -> v.toUpperCase();
    }
}

以下是用于封装该应用程序的 HTTP 服务器的集成测试:

@SpringBootTest(classes = SampleFunctionApplication.class,
            webEnvironment = WebEnvironment.RANDOM_PORT)
public class WebFunctionTests {

    @Autowired
    private TestRestTemplate rest;

    @Test
    public void test() throws Exception {
        ResponseEntity<String> result = this.rest.exchange(
            RequestEntity.post(new URI("/uppercase")).body("hello"), String.class);
        System.out.println(result.getBody());
    }
}

或当 function bean 定义样式被使用:

@FunctionalSpringBootTest
public class WebFunctionTests {

    @Autowired
    private TestRestTemplate rest;

    @Test
    public void test() throws Exception {
        ResponseEntity<String> result = this.rest.exchange(
            RequestEntity.post(new URI("/uppercase")).body("hello"), String.class);
        System.out.println(result.getBody());
    }
}

此测试与您为该应用程序的 @Bean 版本编写的测试几乎完全相同 - 唯一的区别在于 @FunctionalSpringBootTest 注释,而不是常规 @SpringBootTest。所有其他部分,如 @Autowired TestRestTemplate,都是标准的 Spring Boot 功能。

为了帮助解决正确的依赖项,这里是从 POM 中摘录的内容

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    . . . .
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-function-web</artifactId>
        <version>{project-version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

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

@FunctionalSpringBootTest
public class FunctionalTests {

	@Autowired
	private FunctionCatalog catalog;

	@Test
	public void words() {
		Function<String, String> function = catalog.lookup(Function.class,
				"uppercase");
		assertThat(function.apply("hello")).isEqualTo("HELLO");
	}

}