跳到主要内容

功能性的 Bean 定义

DeepSeek V3 中英对照 Functional Bean Definitions

Spring Cloud Function 支持一种“函数式”风格的 Bean 声明,适用于需要快速启动的小型应用程序。这种函数式的 Bean 声明方式是 Spring Framework 5.0 的一个特性,并在 5.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);
}

}
json

现在来看看功能性的 beans:用户应用程序代码可以重构成“功能性”形式,如下所示:

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

}
json

主要区别在于:

  • 主类是一个 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();
}

}
json

如果你添加一个单独的、独立的类型为 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()));
}

}
java

你这样注册它:

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

功能 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
. . .
java

执行器

由于 actuator 和 web 是可选的,你必须首先添加其中一个 web 依赖项,并手动添加 actuator 依赖项。以下示例展示了如何为 Web 框架添加依赖项:

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

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

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

你可以通过以下方式添加 Actuator 依赖:

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

你还需要通过设置以下属性来启用 functions 执行器端点:--management.endpoints.web.exposure.include=functions

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

例如,

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

你的输出应该看起来像这样:

{"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"}. . .
text

测试功能应用程序

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();
}
}
java

这是对该应用程序封装的 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());
}
}
java

或者当使用函数 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());
}
}
java

这个测试与你为相同应用程序的 @Bean 版本编写的测试几乎完全相同——唯一的区别是使用了 @FunctionalSpringBootTest 注解,而不是常规的 @SpringBootTest。所有其他部分,比如 @AutowiredTestRestTemplate,都是 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>4.2.1</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>
xml

或者,你可以使用 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");
}

}
java