|
对于最新稳定版本,请使用 spring-cloud-function 5.0.0! |
编程模型
函数目录与灵活函数签名
Spring Cloud 函数的主要功能之一是能够适应并支持多种类型签名,以适应用户自定义函数,
同时提供一致的执行模型。
这就是为什么所有用户定义的函数都通过以下方式转换为规范表示功能目录.
虽然用户通常不必关心功能目录当然,知道是什么很重要
用户代码支持的函数类型。
还需要理解的是,Spring Cloud Function 为响应式 API 提供了一流的支持
由反应器项目提供的,允许响应式原语,如单和通量作为用户自定义函数中的类型,在选择编程模型时提供更大的灵活性。
你的函数实现。
响应式编程模型还使得对那些原本难以实现甚至不可能的功能提供了功能支持
采用命令式编程风格。欲了解更多内容,请阅读功能数(Function Aity)部分。
Java 8 函数支持
Spring Cloud 函数拥抱并构建在 Java 定义的三个核心功能接口之上 自Java 8起即可使用。
-
提供商<O>
-
功能<I,O>
-
消费者<我>
避免不断提及提供商,功能和消费者在本手册剩余部分,我们会在适当情况下称它们为“功能豆”。
简而言之,应用上下文中任何功能性 bean 都将被懒惰地注册为功能目录.
这意味着它本参考手册中描述的所有额外功能都能受益。
在最简单的应用中,你只需申报@Bean类型提供商,功能或消费者在你的应用配置中。
然后你就可以访问了功能目录并根据函数名称查找特定函数。
例如:
@Bean
public Function<String, String> uppercase() {
return value -> value.toUpperCase();
}
. . .
FunctionCatalog catalog = applicationContext.getBean(FunctionCatalog.class);
Function uppercase = catalog.lookup(“uppercase”);
鉴于这一点,理解这一点很重要大写是豆子,你当然可以从中获得应用上下文直接,但你只能拿到你申报的豆子,没有SCF提供的额外功能。当你通过功能目录您将收到的实例已被本手册中描述的额外功能(如类型转换、组合等)包裹(带入)。此外,需要理解的是,普通用户并不会直接使用 Spring Cloud Function。相反,典型用户会实现 Java职能/提供商/消费者想法是可以在不同的执行环境中使用它,而无需额外工作。例如,同一 Java 函数可以通过 Spring Cloud 函数表示为 REST 端点、流式消息处理程序、AWS Lambda 等
适配器以及其他以 Spring Cloud Function 为核心编程模型的框架(例如 Spring Cloud Stream)。
总之,Spring Cloud Function 为 Java 函数提供了可在多种执行环境中使用的额外功能。
函数定义
虽然前面的例子展示了如何在 FunctionCatalog 中程序化查找函数,但在典型集成情形下,比如 Spring Cloud 函数被其他框架(例如 Spring Cloud Stream)用作编程模型,你可以通过声明使用哪些函数spring.cloud.function.definition财产。了解在发现函数时的一些默认行为非常重要功能目录.比如,如果你只有一个功能性豆子应用上下文这spring.cloud.function.definition通常不需要性质,因为功能目录可以用空名或任意名称查找。例如,假设大写是你目录中唯一的函数,可以查找为catalog.lookup(null),catalog.lookup(“”),catalog.lookup(“foo”).
话虽如此,如果你使用像 Spring Cloud Stream 这样的框架,它会使用spring.cloud.function.definition这是最佳实践,建议始终使用。spring.cloud.function.definition财产。
例如
spring.cloud.function.definition=uppercase
过滤不合格函数
典型的应用上下文可能包含一些是有效的 Java 函数,但并非被设计为候选注册对象的 Beans功能目录.
这些豆子可以是来自其他项目的自动配置,或者任何符合 Java 函数条件的其他豆子。
该框架默认过滤那些不应被功能目录注册的已知豆子。
你也可以通过提供带有逗号分隔的Beans定义名称列表,添加到该列表中,使用spring.cloud.function.ineligible-definitions财产。
例如
spring.cloud.function.ineligible-definitions=foo,bar
提供商
提供商可能反应迅速 - 提供商<Flux<T>>或祈使式 - 提供商<T>.从召唤的角度来看,这应该没有区别
该提供商的实施者。然而,在框架内使用时,
(例如,春云流),提供商,尤其是反应型的,
通常用于表示流的源头,因此只需调用一次即可获得流(例如,Flux)
消费者可以订阅。换句话说,这些提供商相当于无限的流。
然而,相同的响应式提供商也可以表示有限流(例如,轮询后的JDBC数据中的结果集)。
在这种情况下,这些反应性提供商必须连接到底层框架的某种轮询机制。
为了帮助实现这一点,春季云函数提供了一个标记注释org.springframework.cloud.function.context.PollableBean以表示该提供商生产
有限流,可能需要再次轮询。话虽如此,重要的是要理解Spring Cloud Function本身
不提供该注释的行为。
另外可投票豆注释会向产生流的信号暴露一个可拆分属性
需要被拆分(参见分路器EIP)
以下是示例:
@PollableBean(splittable = true)
public Supplier<Flux<String>> someSupplier() {
return () -> {
String v1 = String.valueOf(System.nanoTime());
String v2 = String.valueOf(System.nanoTime());
String v3 = String.valueOf(System.nanoTime());
return Flux.just(v1, v2, v3);
};
}
功能
function 也可以用命令式或响应式写成,但与 Supplier 和 Consumer 不同,Function 有 实现者没有特别的考虑,只是要理解在框架内使用时的理解 如春云流及其他,反应函数为 每次只调用一次以传递对流(Flux 或 Mono)的引用,每事件调用一次命令。
函数合成
函数组合是一种功能,允许将多个函数合成为一个。 核心支持基于自 Java 8 起支持 Function.andThen(..) 的函数组合功能。不过,除此之外,我们还提供一些额外的功能。
功能路由与过滤
自2.2版本起,Spring云函数提供了路由功能,允许 你调用一个函数,作为路由器连接到你想调用的实际函数。 该功能在某些FAAS环境中非常有用,需要维护配置 对于多个函数来说,可能很繁琐,或者无法暴露多个函数。
这路由函数在功能目录中以函数路由.为了简化起见
以及你也可以参考的一致性RoutingFunction.FUNCTION_NAME不断。
该函数具有以下签名:
public class RoutingFunction implements Function<Object, Object> {
. . .
}
路由指令可以通过多种方式传递。我们支持通过消息头部系统提供指令 属性以及可插拔的策略。那么我们来看看一些细节
消息路由回调
这消息路由回调是一种帮助确定路径到函数定义名称的策略。
public interface MessageRoutingCallback {
default String routingResult(Message<?> message) {
return (String) message.getHeaders().get(FunctionProperties.FUNCTION_DEFINITION);
}
}
你只需要实现并注册为一个豆子,这样就能被路由函数.
例如:
@Bean
public MessageRoutingCallback customRouter() {
return new MessageRoutingCallback() {
@Override
public String routingResult(Message<?> message) {
return (String) message.getHeaders().get(FunctionProperties.FUNCTION_DEFINITION);
}
};
}
在前面的例子中,你可以看到一个非常简单的实现消息路由回调确定函数定义FunctionProperties.FUNCTION_DEFINITION消息头,返回 的实例字符串表示调用函数的定义。
消息头
如果输入参数类型为留言<?>,你可以通过设置spring.cloud.function.definition或Spring.cloud.function.routing-expression消息头。
正如该物业名称所示Spring.cloud.function.routing-expression依赖于 Spring 表达语言(SpEL)。
对于更静态的案例,你可以使用spring.cloud.function.definition该头允许你提供
单个函数的名称(例如,…定义=foo)或作文指导(例如,…定义=foo|bar|baz).
对于更动态的情况,你可以使用Spring.cloud.function.routing-expression并提供应当解析的SpEL表达式
定义函数(如上所述)。
SpEL 评估上下文的根对象是
实际输入参数,因此在留言<?>你可以构建一个能够访问的表达式
两人都去有效载荷和头(例如,spring.cloud.function.routing-expression=headers.function_name). |
SpEL允许用户为可执行的Java代码提供字符串表示。鉴于Spring.cloud.function.routing-expression可以通过消息头提供,意味着设置此类表达式的能力可能会暴露给终端用户(例如使用Web模块时的HTTP头),这可能导致一些问题(例如恶意代码)。为此,所有通过Message头部的表达式都将被评估为SimpleEvaluationContext该对象功能有限,且仅设计用于评估上下文对象(我们为Message)。另一方面,所有通过属性或系统变量设置的表达式都需针对 进行评估标准评估上下文,这使得Java语言能够实现完全的灵活性。
虽然通过系统/应用属性或环境变量设置表达式通常被认为是安全的,因为在正常情况下不会向终端用户开放,但在某些情况下,通过Spring Boot Actuator端点(由部分Spring项目、第三方或终端用户定制实现)确实会向终端用户开放系统、应用和环境变量的可视化和更新能力。此类端点必须采用行业标准的网络安全措施进行保护。
Spring Cloud 函数不会暴露任何此类端点。 |
在特定的执行环境/模型中,适配器负责转换和通信spring.cloud.function.definition和/或Spring.cloud.function.routing-expression通过消息头部。
例如,使用 spring-cloud-function-web 时,你可以提供spring.cloud.function.definition作为HTTP
框架会将该头以及其他HTTP头作为消息头传播。
应用属性
路由指令也可以通过以下方式进行传递spring.cloud.function.definition或Spring.cloud.function.routing-expression作为应用属性。以下规则描述
上述部分也适用于此处。唯一的区别是你提供这些说明:
应用属性(例如,--spring.cloud.function.definition=foo).
重要的是要明白,提供spring.cloud.function.definition或Spring.cloud.function.routing-expression因为Message头只能用于命令式函数(例如,功能<福,酒吧>).
也就是说,我们只能用命令式函数为每条消息进行路由。对于响应式函数,我们无法为每个消息进行路由。因此,你只能以应用属性提供路由指令。
这完全是关于工作单位。在命令式功能中,工作单元是消息,因此我们可以基于该工作单元进行路由。
对于响应式函数,工作单元是整个流,因此我们只会根据应用程序提供的信息来作
属性并路由整条溪流。 |
路由指令的优先级顺序
鉴于我们有多种提供路由指令的机制,理解 的优先级非常重要 冲突解决方式是同时使用多种机制,顺序如下:
-
消息路由回调(如果函数是命令式,无论是否定义其他函数,都将接管) -
消息头部(如果函数是命令式且
消息路由回调提供) -
应用属性(任意函数)
无法路由的消息
如果目录中没有路径到功能,你会看到一个例外说明。
有些情况下,这种行为并不理想,你可能需要某种“包容”式的功能来处理这类消息。
为此,框架提供了org.springframework.cloud.function.context.DefaultMessageRoutingHandler策略。你只需要注册为豆子。
其默认实现会记录消息不可路由的事实,但允许消息流继续且不触发该例外,从而有效地丢弃不可路由消息。
如果你想要更复杂的方法,只需提供你自己的实现,并注册为一个“豆子”。
@Bean
public DefaultMessageRoutingHandler defaultRoutingHandler() {
return new DefaultMessageRoutingHandler() {
@Override
public void accept(Message<?> message) {
// do something really cool
}
};
}
功能过滤
过滤是一种只有两条路径的路由方式——“去”或“丢弃”。从函数角度看,这意味着 只有当某个条件返回“true”时才需要调用某个函数,否则你需要丢弃输入。 然而,关于丢弃输入,在你的申请背景下,这可能意味着什么,有许多不同的解释。 例如,你可能想记录,或者想保留丢弃消息的计数器。你也可以什么都不做。 由于这些不同的路径,我们没有提供处理丢弃消息的通用配置选项。 相反,我们建议简单定义一个简单的消费者,表示“丢弃”路径:
@Bean
public Consumer<?> devNull() {
// log, count or whatever
}
现在你可以有只有两条路径的路由表达式,实际上就变成了过滤器。例如:
--spring.cloud.function.routing-expression=headers.contentType.toString().equals('text/plain') ? 'echo' : 'devNull'
每一条不符合“echo”功能的消息都会被送到“devNull”,在那里你根本无法处理。
签名消费者<?>同时确保不会尝试类型转换,几乎没有执行开销。
| 处理响应式输入(例如Publisher)时,路由指令只能通过函数属性提供。这是 由于反应函数的特性,这些函数只需调用一次以传递出版商,其余则是 由反应堆处理,因此我们无法访问和/或依赖通过个别传递的路由指令 值(例如,Message)。 |
多路由器
默认情况下,框架始终配置一个路由功能,如前述所述。不过,有时你可能需要多个路由功能。
在这种情况下,你可以创建自己的实例路由函数除了现有的豆子,只要你给它起个名字,不就是函数路由.
你可以通过Spring.cloud.function.routing-expression或spring.cloud.function.definition映射到RoutinFunction,作为映射中的键/值对。
这里有一个简单的例子
@Configuration
protected static class MultipleRouterConfiguration {
@Bean
RoutingFunction mySpecialRouter(FunctionCatalog functionCatalog, BeanFactory beanFactory, @Nullable MessageRoutingCallback routingCallback) {
Map<String, String> propertiesMap = new HashMap<>();
propertiesMap.put(FunctionProperties.PREFIX + ".routing-expression", "'reverse'");
return new RoutingFunction(functionCatalog, propertiesMap, new BeanFactoryResolver(beanFactory), routingCallback);
}
@Bean
public Function<String, String> reverse() {
return v -> new StringBuilder(v).reverse().toString();
}
@Bean
public Function<String, String> uppercase() {
return String::toUpperCase;
}
}
以及一个演示其工作原理的测试
`
@Test
public void testMultipleRouters() {
System.setProperty(FunctionProperties.PREFIX + ".routing-expression", "'uppercase'");
FunctionCatalog functionCatalog = this.configureCatalog(MultipleRouterConfiguration.class);
Function function = functionCatalog.lookup(RoutingFunction.FUNCTION_NAME);
assertThat(function).isNotNull();
Message<String> message = MessageBuilder.withPayload("hello").build();
assertThat(function.apply(message)).isEqualTo("HELLO");
function = functionCatalog.lookup("mySpecialRouter");
assertThat(function).isNotNull();
message = MessageBuilder.withPayload("hello").build();
assertThat(function.apply(message)).isEqualTo("olleh");
}
[[输入/输出丰富]] == 输入/输出富集
通常你需要修改或优化进出消息,并保持代码干净,避免非功能性问题。你不想在业务逻辑里做这件事。
你总可以通过函数写作来实现。这种方法带来了多项好处:
-
它允许你将这个非功能性关注点隔离到一个独立的函数中,你可以用业务函数作为函数定义来组合。
-
它给你完全的自由(也包括风险)在收到消息到达实际业务岗位之前可以修改的内容。
@Bean
public Function<Message<?>, Message<?>> enrich() {
return message -> MessageBuilder.fromMessage(message).setHeader("foo", "bar").build();
}
@Bean
public Function<Message<?>, Message<?>> myBusinessFunction() {
// do whatever
}
然后通过提供以下函数定义来组合你的函数enrich|myBusinessFunction.
虽然描述的方法最灵活,但也最复杂,因为它需要写代码、做成简单代码或 在你用业务函数组合之前,先手动注册它作为函数,正如前面示例所示。
但如果你试图做的修改(丰富)像前面的例子一样微不足道呢?有没有更简单、更动态且可配置的 有没有实现同样效果的机制?
自3.1.3版本起,框架允许你提供SpEL表达式,丰富单个消息头,用于函数和 以及从中输出出来的。我们来看看其中一个测试作为例子。
@Test
public void testMixedInputOutputHeaderMapping() throws Exception {
try (ConfigurableApplicationContext context = new SpringApplicationBuilder(
SampleFunctionConfiguration.class).web(WebApplicationType.NONE).run(
"--logging.level.org.springframework.cloud.function=DEBUG",
"--spring.main.lazy-initialization=true",
"--spring.cloud.function.configuration.split.output-header-mapping-expression.keyOut1='hello1'",
"--spring.cloud.function.configuration.split.output-header-mapping-expression.keyOut2=headers.contentType",
"--spring.cloud.function.configuration.split.input-header-mapping-expression.key1=headers.path.split('/')[0]",
"--spring.cloud.function.configuration.split.input-header-mapping-expression.key2=headers.path.split('/')[1]",
"--spring.cloud.function.configuration.split.input-header-mapping-expression.key3=headers.path")) {
FunctionCatalog functionCatalog = context.getBean(FunctionCatalog.class);
FunctionInvocationWrapper function = functionCatalog.lookup("split");
Message<byte[]> result = (Message<byte[]>) function.apply(MessageBuilder.withPayload("helo")
.setHeader(MessageHeaders.CONTENT_TYPE, "application/json")
.setHeader("path", "foo/bar/baz").build());
assertThat(result.getHeaders().containsKey("keyOut1")).isTrue();
assertThat(result.getHeaders().get("keyOut1")).isEqualTo("hello1");
assertThat(result.getHeaders().containsKey("keyOut2")).isTrue();
assertThat(result.getHeaders().get("keyOut2")).isEqualTo("application/json");
}
}
这里你会看到一个称为输入-头-映射-表达式和输出-头-映射-表达式前置函数名称(即,分裂) 后跟你想设置的消息头键名和 SpEL 表达式的值。第一个表达式(针对“keyOut1”)是用单引号包住的字面SpEL表达式,实际上将“keyOut1”设为值你好1.这keyOut2设置为现有“contentType”头部的值。
你还可以在输入头映射中观察到一些有趣的特征,比如我们实际上将现有头部“路径”的值拆分,将key1和key2的单个值设置为基于索引的拆分元素值。
| 如果由于某种原因,所提供的表达式的求值失败,函数的执行将如同从未发生过一样继续。 不过你会在日志中看到WARN提示,告知你相关情况 |
o.s.c.f.context.catalog.InputEnricher : Failed while evaluating expression "hello1" on incoming message. . .
如果你处理的是有多个输入的函数(下一节),你可以在之后立即使用 index。输入-头-映射-表达式
--spring.cloud.function.configuration.echo.input-header-mapping-expression[0].key1=‘hello1'
--spring.cloud.function.configuration.echo.input-header-mapping-expression[1].key2='hello2'
函数元数
有时数据流需要分类和整理。例如 考虑一个经典的大数据用例,处理包含无序数据的例子,比如说, “订单”和“发票”,你希望它们分别存放在一个独立的数据存储中。 这正是函数元(具有多输入和多输出的函数)支持的地方 来发挥作用。
让我们来看一个这样的函数示例(完整的实现细节在这里),
@Bean
public Function<Flux<Integer>, Tuple2<Flux<String>, Flux<String>>> organise() {
return flux -> ...;
}
鉴于Project Reactor是SCF的核心依赖,我们使用了它的元库库。 元组通过向我们传递基数和类型信息,给了我们独特的优势。 这两者在SCSt的语境中都极为重要。基数让我们知道 需要创建多少输入和输出绑定,并绑定到对应的 函数的输入和输出。对类型信息的了解确保正确的类型 转换。
此外,这就是命名规则中“索引”部分用于装订的地方
名称很重要,因为在此函数中,两者输出绑定
名称如下组织-出-0和组织出局-1.
重要提示:目前,函数元仅支持反应函数
(函数<TupleN<Flux<?>......>,TupleN<Flux<?>......>>)以复杂事件处理为中心
其中对事件汇流的评估和计算通常需要对
事件流而非单一事件。 |
输入头传播
在典型场景中,输入消息头不会传播到输出,这也是理所当然的,因为函数的输出可能是其他需要独立消息头集合的输入。 然而,有时这种传播是必要的,因此Spring Cloud Function提供了多种机制来实现这一目标。
首先,你总可以手动复制头部。例如,如果你有一个函数,其签名为消息以及返回消息(即,功能<消息,消息>),你可以简单且有选择地自己复制页头。记住,如果你的函数返回了 Message,框架不会对它做任何作,除了正确转换其有效载荷。
然而,这种方法可能会显得有些繁琐,尤其是在你只是想复制所有头部的情况下。
为了帮助这种情况,我们提供了一个简单的属性,允许你在需要传播输入头部的函数上设置布尔标志。
该性质为复制输入头.
例如,假设你的配置如下:
@EnableAutoConfiguration
@Configuration
protected static class InputHeaderPropagationConfiguration {
@Bean
public Function<String, String> uppercase() {
return x -> x.toUpperCase();
}
}
如你所知,你仍然可以通过发送消息调用这个函数(框架会处理类型转换和有效载荷提取)。
简单地设置spring.cloud.function.configuration.uppercase.copy-input-headers自true,以下断言同样成立
Function<Message<String>, Message<byte[]>> uppercase = catalog.lookup("uppercase", "application/json");
Message<byte[]> result = uppercase.apply(MessageBuilder.withPayload("bob").setHeader("foo", "bar").build());
assertThat(result.getHeaders()).containsKey("foo");
类型转换(内容-类型协商)
内容类型协商是 Spring Cloud Function 的核心功能之一,因为它不仅允许将输入数据转换为声明的类型 通过函数签名,但在函数复合时进行相同的变换,使得原本无法组合(按类型)的函数可组合。
为了更好地理解内容类型协商的机制和必要性,我们来看一个非常简单的用例: 以以下函数为例:
@Bean
public Function<Person, String> personFunction {..}
前述示例中所示的函数期望为人object 作为参数,输出 String 类型。如果这样的函数为
与类型一起调用人,然后一切正常。但通常函数是处理接收数据的处理者,这些数据最常被接收到
在RAW格式中,如字节[],JSON 字符串等。为了让框架成功将输入数据作为参数传递给
这个函数必须以某种方式将输入数据转换为人类型。
Spring Cloud Function 依赖于 Spring 原生的两种机制来实现这一点。
-
MessageConverter - 将接收的消息数据转换为函数声明的类型。
-
ConversionService——将非消息数据转换为函数声明的类型。
这意味着根据原始数据的类型(消息或非消息),Spring Cloud 函数会应用其中一种机制。
在大多数情况下,当处理作为其他请求(例如HTTP、消息等)调用的函数时,框架依赖于消息转换器,
因为此类请求已转换为春季消息.换句话说,该框架定位并应用了适当的消息转换器.
为了实现这一点,框架需要用户提供一些指令。其中一条指令已经由函数的签名给出
本身(人型)。因此,理论上,这应该(在某些情况下确实)足够。然而,对于大多数使用场景来说,为了
选择合适的消息转换器,框架还需要补充一条信息。那个缺失的拼图是内容类型页眉。
此类头部通常作为消息的一部分出现,由最初创建该消息的相应适配器注入。
例如,HTTP POST 请求的内容类型 HTTP 头部会被复制到内容类型消息的头部。
对于不存在此类头部的情况,框架依赖默认内容类型application/json.
内容类型与论元类型
如前所述,框架选择合适的消息转换器它需要参数类型,并且可选地提供内容类型信息。
选择合适 的逻辑消息转换器存在于参数解析器,该参数在调用用户定义文件前触发
函数(即框架已知实际参数类型)。
如果参数类型与当前有效载荷的类型不匹配,框架会委派给
预配置消息转换器看看他们中是否有人能转换有效载荷。
组合内容类型参数类型是框架判断消息是否可以通过定位转换为目标类型的机制
适当的消息转换器.
如果不合适消息转换器如果被发现,就会抛出一个异常,你可以通过添加自定义来处理消息转换器(参见用户自定义消息转换器).
别抱太大期望消息仅基于内容类型.
记住内容类型与目标类型互补。
这是一个暗示,消息转换器可能会考虑,也可能不会。 |
消息转换器
消息转换器定义两种方法:
Object fromMessage(Message<?> message, Class<?> targetClass);
Message<?> toMessage(Object payload, @Nullable MessageHeaders headers);
了解这些方法的契约及其使用方式非常重要,特别是在春云流的背景下。
这发件消息方法转换一个输入消息变成了争吵类型。
有效载荷消息可以是任何类型,它
直到实际实现消息转换器支持多种类型。
提供的消息转换器
如前所述,该框架已经提供了消息转换器以应对大多数常见的使用场景。
以下列表描述了所提供的消息转换器,按优先顺序(第一个)消息转换器该方法被使用):
-
JsonMessageConverter支持对 的有效载荷转换消息对于当内容类型是application/json使用Jackson(默认)或Gson库。该消息转换器还知道类型参数(例如,application/json;type=foo.bar.Person)。这对于在函数开发时类型可能未知的情况非常有用,因此函数签名可能看起来像功能<?, ?>或功能或功能<对象,对象>.换句话说,对于类型转换,我们通常从函数签名中推导出类型。拥有 mime 类型的参数可以让你以更动态的方式传达类型。 -
字节阵列消息转换器支持对 的有效载荷转换消息从字节[]自字节[]对于内容类型是应用/八位元组流.它本质上是一个直通,主要用于向下兼容。 -
字符串消息转换器:支持任意类型的转换字符串什么时候内容类型是文本/纯文字.
当找不到合适的转换器时,框架会抛出异常。遇到这种情况时,你应该检查代码和配置,确保你做了
不要遗漏任何内容(也就是说,确保你提供了内容类型通过使用绑定或头部)。
不过,很可能你遇到了一些不常见的情况(比如习惯内容类型也许)以及当前提供的堆栈消息转换器不知道如何转换。如果是这样,你可以添加自定义消息转换器.参见用户自定义消息转换器。
用户自定义消息转换器
Spring Cloud 函数提供了定义和注册额外信息的机制消息转换器.
要用它,就实现org.springframework.messaging.converter.MessageConverter,将其配置为@Bean.
然后它被附加到现有的“MessageConverter”堆栈中。
理解这种习俗非常重要消息转换器实现会被添加到现有栈的头部。
因此,习俗消息转换器实现优先于现有的,这让你既能覆盖也能添加现有转换器。 |
以下示例展示了如何创建消息转换器豆,以支持名为应用/律师资格:
@SpringBootApplication
public static class SinkApplication {
...
@Bean
public MessageConverter customMessageConverter() {
return new MyCustomMessageConverter();
}
}
public class MyCustomMessageConverter extends AbstractMessageConverter {
public MyCustomMessageConverter() {
super(new MimeType("application", "bar"));
}
@Override
protected boolean supports(Class<?> clazz) {
return (Bar.class.equals(clazz));
}
@Override
protected Object convertFromInternal(Message<?> message, Class<?> targetClass, Object conversionHint) {
Object payload = message.getPayload();
return (payload instanceof Bar ? payload : new Bar((byte[]) payload));
}
}
关于JSON选项的说明
在 Spring Cloud Function 中,我们支持 Jackson 和 Gson 机制来处理 JSON。
为了方便你,我已经将其抽象化为以下org.springframework.cloud.function.json.JsonMapper而 S 本身也知道两种机制,并会使用所选的那一种
是你做的,还是遵循默认规则。
默认规则如下:
-
无论类路径上哪个库,那就是将要使用的机制。所以如果你有
com.fasterxml.jackson.*到班级路径,Jackson将被使用,如果你已经使用com.google.code.gson,则使用 Gson。 -
如果你两者都有,那么Gson将成为默认选项,或者你可以设置
Spring.cloud.function.preferred-json-mapper具有以下两个取值中的任意一个的属性:孙或Jackson.
不过,类型转换通常对开发者来说是透明的,但前提是org.springframework.cloud.function.json.JsonMapper也注册为豆子
如果需要,你可以很容易地把它注入代码里。
Kotlin Lambda 支持
我们也支持 Kotlin lambda(自 v2.0 起)。 请考虑以下几点:
@Bean
open fun kotlinSupplier(): () -> String {
return { "Hello from Kotlin" }
}
@Bean
open fun kotlinFunction(): (String) -> String {
return { it.toUpperCase() }
}
@Bean
open fun kotlinConsumer(): (String) -> Unit {
return { println(it) }
}
上面表示了Kotlin lambdas的配置为春豆。每个的签名映射到 Java 等价的提供商,功能和消费者因此,框架支持/认可签名。
虽然Kotlin到Java映射的机制不在本文档范围内,但重要的是要理解
这里也应用了“Java 8 函数支持”部分中描述的签名转换规则。
要启用Kotlin支持,只需在类路径上添加Kotlin SDK库,它就会触发相应的 自动配置和支持类。
功能组件扫描
Spring Cloud 函数将扫描 的实现功能,消费者和提供商在一个名为功能如果它存在的话。利用这个
你可以编写对 Spring 没有依赖的函数——甚至不依赖于@Component需要注释。如果你想用不同的方法
package,你可以设置spring.cloud.function.scan.packages.你也可以使用spring.cloud.function.scan.enabled=false完全关闭扫描。
数据掩蔽
典型的应用包含多个层次的记录。某些云/无服务器平台可能会在数据包中包含敏感数据,供所有人查看。
虽然检查被记录的数据是个别开发者的责任,但日志记录来自框架本身,因此自4.1版本起我们引入了JsonMasker最初帮助掩蔽AWS Lambda载荷中的敏感数据。然而,JsonMasker是通用的,适用于任何模块。目前它只能支持像JSON这样的结构化数据。你只需要指定想要遮蔽的密钥,其他的都会被它处理。
密钥应在文件中指定META-INF/mask.keys.文件格式非常简单,可以用逗号、换行或两者兼有来分隔多个键。
以下是该文件内容的示例:
eventSourceARN asdf1, SS
这里你可以看到定义了三个密钥 一旦有这样的文件,JsonMasker 就会用它来遮蔽指定键的值。
这里有示例代码,展示了使用情况
private final static JsonMasker masker = JsonMasker.INSTANCE();
. . .
logger.info("Received: " + masker.mask(new String(payload, StandardCharsets.UTF_8)));