函数式 Bean 定义
Spring Cloud Function 支持“函数式”风格的 Bean 声明,适用于需要快速启动的小型应用程序。函数式 Bean 声明风格是 Spring Framework 5.0 的一个特性,并在 5.1 中得到了显著增强。
函数式与传统 Bean 定义的比较
这是一个普通的 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);
}
}
现在是函数式 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,表示我们未启用 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,它也将工作。主要的是通过类声明在运行时可用的泛型类型信息。
假设您有
@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));
}
函数式 Bean 声明的局限性
大多数 Spring Cloud Function 应用程序的范围相对于整个 Spring Boot 来说相对较小,因此我们能够轻松地将其适应这些函数式 Bean 定义。如果您超出该有限范围,可以通过切换回 @Bean 风格配置或采用混合方法来扩展您的 Spring Cloud Function 应用程序。例如,如果您想利用 Spring Boot 自动配置与外部数据存储进行集成,则需要使用 @EnableAutoConfiguration。如果您愿意,您的函数仍然可以使用函数式声明(即“混合”风格)进行定义,但在这种情况下,您需要通过设置 spring.functional.enabled=false 明确关闭“完全函数式模式”,以便 Spring Boot 能够重新获得控制权。
函数可视化和控制
Spring Cloud Function 支持通过 Actuator 端点和编程方式可视化 FunctionCatalog 中可用的函数。
编程方式
要以编程方式查看应用程序上下文中可用的函数,您所需要的只是对 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 中的函数:<host>:<port>/actuator/functions
例如,
curl https://: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"}. . .
测试函数式应用程序
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());
}
}
或者当使用函数 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>5.0.0</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");
}
}