Functional Bean Definitions
Spring Cloud Function 支持小应用程序的功能样式 bean 声明,这些应用程序需要快速启动。功能样式的 bean 声明是 Spring Framework 5.0 的一项功能,在 5.1 中得到显著增强。
Spring Cloud Function supports a "functional" style of bean declarations for small apps where you need fast startup. The functional style of bean declaration was a feature of Spring Framework 5.0 with significant enhancements in 5.1.
Comparing Functional with Traditional Bean Definitions
下面是一个来自熟悉的 @Configuration
和 @Bean
声明样式的纯 Spring Cloud Function 应用程序:
Here’s a vanilla Spring Cloud Function application from with the
familiar @Configuration
and @Bean
declaration style:
@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:用户应用程序代码可以重新转换为“功能”形式,如下所示:
Now for the functional beans: the user application code can be recast into "functional" form, like this:
@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)));
}
}
主要差异如下:
The main differences are:
-
The main class is an
ApplicationContextInitializer
. -
The
@Bean
methods have been converted to calls tocontext.registerBean()
-
The
@SpringBootApplication
has been replaced with@SpringBootConfiguration
to signify that we are not enabling Spring Boot autoconfiguration, and yet still marking the class as an "entry point". -
The
SpringApplication
from Spring Boot has been replaced with aFunctionalSpringApplication
from Spring Cloud Function (it’s a subclass).
在 Spring Cloud Function 应用程序中注册的业务逻辑 bean 类型为 FunctionRegistration
。这是一个包装器,其中包含有关输入类型和输出类型的功能和信息。在应用程序的 @Bean
形式中,可以通过反射导出该信息,但在功能 bean 注册中,除非我们使用 FunctionRegistration
,否则其中一些信息会丢失。
The business logic beans that you register in a Spring Cloud Function app are of type FunctionRegistration
.
This is a wrapper that contains both the function and information about the input and output types. In the @Bean
form of the application that information can be derived reflectively, but in a functional bean registration some of
it is lost unless we use a FunctionRegistration
.
使用 ApplicationContextInitializer
和 FunctionRegistration
的替代方法是让应用程序本身实现 Function
(或 Consumer
或 Supplier
)。示例(等效于上述示例):
An alternative to using an ApplicationContextInitializer
and FunctionRegistration
is to make the application
itself implement Function
(or Consumer
or Supplier
). Example (equivalent to the above):
@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
注册它,也可以工作。最重要的是,泛型类型信息在运行时通过类声明提供。
It would also work if you add a separate, standalone class of type Function
and register it with
the SpringApplication
using an alternative form of the run()
method. The main thing is that the generic
type information is available at runtime through the class declaration.
假设您有
Suppose you have
@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()));
}
}
您已将其注册为:
You register it as such:
@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 可以重新控制。
Most Spring Cloud Function apps have a relatively small scope compared to the whole of Spring Boot,
so we are able to adapt it to these functional bean definitions easily. If you step outside that limited scope,
you can extend your Spring Cloud Function app by switching back to @Bean
style configuration, or by using a hybrid
approach. If you want to take advantage of Spring Boot autoconfiguration for integrations with external datastores,
for example, you will need to use @EnableAutoConfiguration
. Your functions can still be defined using the functional
declarations if you want (i.e. the "hybrid" style), but in that case you will need to explicitly switch off the "full
functional mode" using spring.functional.enabled=false
so that Spring Boot can take back control.
Function visualization and control
Spring Cloud Function 支持通过 Actuator 端点以及编程方式可视化 FunctionCatalog
中可用的函数。
Spring Cloud Function supports visualization of functions available in FunctionCatalog
through Actuator endpoints as well as programmatic way.
Programmatic way
要通过编程方式查看应用程序上下文中可用的函数,您只需要访问 FunctionCatalog
。在那里,您可以找到获取编目大小、查找函数以及列出所有可用函数名称的方法。
To see function available within your application context programmatically all you need is access to FunctionCatalog
. There you can
finds methods to get the size of the catalog, lookup functions as well as list the names of all the available functions.
例如,
For example,
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 框架的依赖项:
Since actuator and web are optional, you must first add one of the web dependencies as well as add the actuator dependency manually. The following example shows how to add the dependency for the Web framework:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
以下示例展示了如何添加 WebFlux 框架的依赖项:
The following example shows how to add the dependency for the WebFlux framework:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
您可以按如下方式添加 Actuator 依赖项:
You can add the Actuator dependency as follows:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
您还必须通过设置以下属性来启用 functions
Actuator 端点:--management.endpoints.web.exposure.include=functions
。
You must also enable the functions
actuator endpoints by setting the following property: --management.endpoints.web.exposure.include=functions
.
访问以下 URL 以查看 FunctionCatalog 中的函数:http://<host>:<port>/actuator/functions
Access the following URL to see the functions in FunctionCatalog:
http://<host>:<port>/actuator/functions
例如,
For example,
curl http://localhost:8080/actuator/functions
您的输出应如下所示:
Your output should look something like this:
{"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 用户来说非常熟悉的用于集成测试的实用工具。
Spring Cloud Function also has some utilities for integration testing that will be very familiar to Spring Boot users.
假设这是您的应用程序:
Suppose this is your application:
@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 服务器的集成测试:
Here is an integration test for the HTTP server wrapping this application:
@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 定义样式被使用:
or when function bean definition style is used:
@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 功能。
This test is almost identical to the one you would write for the @Bean
version of the same app - the only difference
is the @FunctionalSpringBootTest
annotation, instead of the regular @SpringBootTest
. All the other pieces,
like the @Autowired
TestRestTemplate
, are standard Spring Boot features.
为了帮助解决正确的依赖项,这里是从 POM 中摘录的内容
And to help with correct dependencies here is the excerpt from 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 应用程序编写测试。例如:
Or you could write a test for a non-HTTP app using just the FunctionCatalog
. For example:
@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");
}
}