|
如需使用最新稳定版本,请使用 Spring Integration 7.0.4! |
内容增强器
有时,您可能需要为请求添加比目标系统提供的更多信息。 数据增强器模式描述了各种场景以及用于满足此类需求的组件(Enricher)。
Spring Integration Core 模块包含两个增强器:
它还包含三个特定于适配器的标头增强器:
请参阅本参考手册中特定于适配器的部分,以了解更多有关这些适配器的信息。
有关表达式支持的更多信息,请参见 Spring 表达式语言 (SpEL)。
头部信息增强器
如果您只需要向消息添加头部,且这些头部并非由消息内容动态确定,那么引用自定义的转换器实现可能有些小题大做。
出于这个原因,Spring Integration 提供了对“头部增强器”模式的支持。
它通过 <header-enricher> 元素暴露出来。
以下示例展示了如何使用它:
<int:header-enricher input-channel="in" output-channel="out">
<int:header name="foo" value="123"/>
<int:header name="bar" ref="someBean"/>
</int:header-enricher>
标题增强器还提供了有用的子元素来设置常见的标题名称,如下例所示:
<int:header-enricher input-channel="in" output-channel="out">
<int:error-channel ref="applicationErrorChannel"/>
<int:reply-channel ref="quoteReplyChannel"/>
<int:correlation-id value="123"/>
<int:priority value="HIGHEST"/>
<routing-slip value="channel1; routingSlipRoutingStrategy; request.headers[myRoutingSlipChannel]"/>
<int:header name="bar" ref="someBean"/>
</int:header-enricher>
上述配置表明,对于已知的头部(例如 errorChannel, correlationId, priority, replyChannel, routing-slip 等),您可以直接使用便捷的子元素来设置这些值,而无需使用通用的 <header> 子元素(在通用子元素中您需要同时提供头部的“名称”和“值”)。
从版本 4.1 开始,头部增强器提供了一个 routing-slip 子元素。
有关更多信息,请参阅 路由单。
POJO 支持
通常,标头值无法静态定义,而必须根据消息中的某些内容动态确定。
这就是为什么标头增强器还允许您使用 ref 和 method 属性来指定 Bean 引用。
指定的方法将计算标头值。
请考虑以下配置以及一个用于修改 String 的方法的 Bean:
<int:header-enricher input-channel="in" output-channel="out">
<int:header name="something" method="computeValue" ref="myBean"/>
</int:header-enricher>
<bean id="myBean" class="thing1.thing2.MyBean"/>
public class MyBean {
public String computeValue(String payload){
return payload.toUpperCase() + "_US";
}
}
您也可以将 POJO 配置为内部 Bean,如下示例所示:
<int:header-enricher input-channel="inputChannel" output-channel="outputChannel">
<int:header name="some_header">
<bean class="org.MyEnricher"/>
</int:header>
</int:header-enricher>
您可以类似地指向 Groovy 脚本,如下示例所示:
<int:header-enricher input-channel="inputChannel" output-channel="outputChannel">
<int:header name="some_header">
<int-groovy:script location="org/SampleGroovyHeaderEnricher.groovy"/>
</int:header>
</int:header-enricher>
SpEL 支持
在 Spring Integration 2.0 中,我们引入了Spring 表达式语言(SpEL)的便利性,以帮助配置许多不同的组件。 标头增强器就是其中之一。 再次查看前面展示的 POJO 示例。 您可以看到,用于确定标头值的计算逻辑非常简单。 一个自然的问题是:“是否有更简单的方法来实现这一点?”。 这正是 SpEL 展现其真正威力的地方。 考虑以下示例:
<int:header-enricher input-channel="in" output-channel="out">
<int:header name="foo" expression="payload.toUpperCase() + '_US'"/>
</int:header-enricher>
通过使用 SpEL 处理此类简单情况,您无需再提供单独的类并在应用程序上下文中对其进行配置。
您只需将 expression 属性配置为有效的 SpEL 表达式即可。
'payload' 和 'headers' 变量已绑定到 SpEL 求值上下文,使您可以完全访问传入的消息。
使用 Java 配置配置 Header Enricher
以下两个示例展示了如何使用 Java 配置进行头部信息增强:
@Bean
@Transformer(inputChannel = "enrichHeadersChannel", outputChannel = "emailChannel")
public HeaderEnricher enrichHeaders() {
Map<String, ? extends HeaderValueMessageProcessor<?>> headersToAdd =
Collections.singletonMap("emailUrl",
new StaticHeaderValueMessageProcessor<>(this.imapUrl));
HeaderEnricher enricher = new HeaderEnricher(headersToAdd);
return enricher;
}
@Bean
@Transformer(inputChannel="enrichHeadersChannel", outputChannel="emailChannel")
public HeaderEnricher enrichHeaders() {
Map<String, HeaderValueMessageProcessor<?>> headersToAdd = new HashMap<>();
headersToAdd.put("emailUrl", new StaticHeaderValueMessageProcessor<String>(this.imapUrl));
Expression expression = new SpelExpressionParser().parseExpression("payload.from[0].toString()");
headersToAdd.put("from",
new ExpressionEvaluatingHeaderValueMessageProcessor<>(expression, String.class));
HeaderEnricher enricher = new HeaderEnricher(headersToAdd);
return enricher;
}
第一个示例添加单个字面量标头。 第二个示例添加两个标头,一个为字面量标头,另一个基于 SpEL 表达式。
使用 Java DSL 配置 Header Enricher
以下示例展示了用于标头增强器的 Java DSL 配置:
@Bean
public IntegrationFlow enrichHeadersInFlow() {
return f -> f
...
.enrichHeaders(h -> h.header("emailUrl", this.emailUrl)
.headerExpression("from", "payload.from[0].toString()"))
.handle(...);
}
头部通道注册表
从 Spring Integration 3.0 开始,提供了一个新的子元素 <int:header-channels-to-string/>。
它没有属性。
这个新的子元素将现有的 replyChannel 和 errorChannel 消息头(当它们是 MessageChannel 时)转换为 String,并将通道存储在注册表中以便后续解析,在需要发送回复或处理错误时使用。
这在消息头可能会丢失的情况下非常有用——例如,将消息序列化到消息存储中,或通过 JMS 传输消息时。
如果该消息头尚不存在,或者它不是 MessageChannel,则不会进行任何更改。
使用此功能需要存在一个 HeaderChannelRegistry Bean。
默认情况下,框架会创建一个具有默认过期时间(60 秒)的 DefaultHeaderChannelRegistry。
在此时间之后,通道将从注册表中移除。
若要更改此行为,请定义一个 id 为 integrationHeaderChannelRegistry 的 Bean,并通过构造函数参数(以毫秒为单位)配置所需的默认延迟。
自 4.1 版本起,您可以在 <bean/> 定义上将名为 removeOnGet 的属性设置为 true,映射条目将在首次使用时立即被移除。
这在处理高吞吐量环境且通道仅使用一次的情况下可能非常有用,而无需等待清理程序将其移除。
HeaderChannelRegistry 拥有一个 size() 方法,用于确定注册表的当前大小。
runReaper() 方法会取消当前计划的任务并立即运行清理程序。
随后,任务将根据当前的延迟重新安排执行。
可以通过获取注册表的引用来直接调用这些方法,或者向控制总线发送包含以下内容的消息:
"integrationHeaderChannelRegistry.runReaper"
此子元素是一个便利选项,相当于指定以下配置:
<int:reply-channel
expression="@integrationHeaderChannelRegistry.channelToChannelName(headers.replyChannel)"
overwrite="true" />
<int:error-channel
expression="@integrationHeaderChannelRegistry.channelToChannelName(headers.errorChannel)"
overwrite="true" />
从 4.1 版本开始,您可以覆盖注册表配置的清理器延迟,以确保通道映射至少保留指定的时间,而不管清理器延迟如何。 以下示例展示了如何实现这一点:
<int:header-enricher input-channel="inputTtl" output-channel="next">
<int:header-channels-to-string time-to-live-expression="120000" />
</int:header-enricher>
<int:header-enricher input-channel="inputCustomTtl" output-channel="next">
<int:header-channels-to-string
time-to-live-expression="headers['channelTTL'] ?: 120000" />
</int:header-enricher>
在第一种情况下,每个头通道映射的存活时间将为两分钟。 在第二种情况下,存活时间在消息头中指定,并使用 Elvis 运算符:如果不存在头信息,则使用两分钟。
有效负载增强器
在某些情况下,前面讨论的头部增强器可能不够用,需要直接对负载本身进行增强,添加额外信息。 例如,进入 Spring Integration 消息系统的订单消息必须根据提供的客户编号查找对应的客户,然后将该信息丰富到原始负载中。
Spring Integration 2.1 引入了负载增强器。
负载增强器定义了一个端点,该端点将 Message 传递到暴露的请求通道,然后期望收到回复消息。
回复消息随后成为评估表达式以增强目标负载的根对象。
负载丰富器通过 enricher 元素提供完整的 XML 命名空间支持。
为了发送请求消息,负载丰富器拥有一个 request-channel 属性,允许您将消息分发到请求通道。
基本上,通过定义请求通道,负载增强器充当网关,等待发送到请求通道的消息返回。 然后,增强器使用回复消息提供的数据来扩充消息的负载。
向请求通道发送消息时,您也可以选择使用 request-payload-expression 属性仅发送原始负载的子集。
有效载荷的丰富化是通过 SpEL 表达式配置的,提供了最大程度的灵活性。
因此,您不仅可以使用回复通道的 Message 中的直接值来丰富有效载荷,还可以使用 SpEL 表达式从该消息中提取子集或应用额外的内联转换,从而进一步操作数据。
如果您只需要用静态值丰富负载,则无需提供 request-channel 属性。
| 富集器是转换器的一种变体。 在许多情况下,您可以使用有效负载富集器或通用转换器实现来向消息有效负载添加额外数据。 您应熟悉 Spring Integration 提供的所有具备转换能力的组件,并仔细选择语义上最符合您业务场景的实现方案。 |
配置
以下示例展示了有效负载增强器的所有可用配置选项:
<int:enricher request-channel="" (1)
auto-startup="true" (2)
id="" (3)
order="" (4)
output-channel="" (5)
request-payload-expression="" (6)
reply-channel="" (7)
error-channel="" (8)
send-timeout="" (9)
should-clone-payload="false"> (10)
<int:poller></int:poller> (11)
<int:property name="" expression="" null-result-expression="'Could not determine the name'"/> (12)
<int:property name="" value="23" type="java.lang.Integer" null-result-expression="'0'"/>
<int:header name="" expression="" null-result-expression=""/> (13)
<int:header name="" value="" overwrite="" type="" null-result-expression=""/>
</int:enricher>
| 1 | 发送消息以获取用于丰富数据的通道。 可选。 |
| 2 | 生命周期属性,指示该组件是否应在应用程序上下文启动期间启动。 默认为 true。 可选。 |
| 3 | 底层 bean 定义的 ID,该值为 EventDrivenConsumer 或 PollingConsumer。
可选。 |
| 4 | 指定当此端点作为订阅者连接到通道时的调用顺序。 当该通道使用“故障转移”分发策略时,这一点尤为重要。 如果此端点本身是带有队列的通道的轮询消费者,则此设置无效。 可选。 |
| 5 | 标识消息经过此端点处理后发送到的消息通道。 可选。 |
| 6 | 默认情况下,原始消息的负载被用作发送到 request-channel 的负载。
通过为 request-payload-expression 属性指定 SpEL 表达式作为值,您可以使用原始负载的子集、头信息值或任何其他可解析的 SpEL 表达式作为发送到请求通道的负载的基础。
对于表达式求值,完整消息可作为“根对象”使用。
例如,以下 SpEL 表达式(包括但不限于)是可行的:payload.something、headers.something、new java.util.Date()、'thing1' + 'thing2' |
| 7 | 期望收到回复消息的通道。 这是可选的。 通常,自动生成的临时回复通道就足够了。 可选。 |
| 8 | 如果 request-channel 下游发生 Exception,则发送 ErrorMessage 的通道。
这使您可以返回一个替代对象用于丰富数据。
如果未设置,则会向调用者抛出 Exception。
可选。 |
| 9 | 在发送消息到通道时,如果通道可能会阻塞,等待的最大毫秒数。
例如,如果队列通道的最大容量已达到,它可以阻塞直到有可用空间。
在内部,send() 超时被设置在 MessagingTemplate 上,并最终在调用 MessageChannel 上的发送操作时应用。
默认情况下,send() 超时设置为 '30'。
可选。 |
| 10 | 布尔值,指示是否应在将任何实现 Cloneable 的有效载荷发送到请求通道以获取丰富数据之前对其进行克隆。
克隆后的版本将用作最终回复的目标有效载荷。
默认值为 false。
可选。 |
| 11 | 如果此端点是轮询消费者,则允许您配置消息轮询器。 可选。 |
| 12 | 每个 property 子元素提供属性的名称(通过必需的 name 属性)。该属性应当可以在目标负载实例上进行设置。必须提供 value 或 expression 属性中的且仅其中一个——前者用于设置字面量值,后者用于评估 SpEL 表达式。评估上下文的根对象是由该 enricher 启动的流返回的消息——如果没有请求通道,则为输入消息;否则为应用程序上下文(使用 @<beanName>.<beanProperty> SpEL 语法)。从版本 4 开始。0,在指定 value 属性时,您还可以选择性地指定 type 属性。当目标是一个类型化的 setter 方法时,框架会适当地强制转换该值(只要存在一个 PropertyEditor 来处理转换)。然而,如果目标有效负载为 Map,则条目将直接填充该值而无需转换。type 属性允许您将包含数字的 String 转换为目标有效载荷中的 Integer 值。从版本 4 开始。1,您还可以指定一个可选的 null-result-expression 属性。当 enricher 返回 null 时,将进行求值,并返回求值的输出结果。 |
| 13 | 每个 header 子元素提供消息头的名称(通过必需的 name 属性)。必须同时提供 value 或 expression 属性中的恰好一个——前者用于设置字面量值,后者用于求值 SpEL 表达式。评估上下文的根对象是由该丰富器启动的流程返回的消息——如果没有请求通道,则为输入消息;否则为应用程序上下文(使用 '@<header-enricher>,<enricher> 元素的 header 元素具有 type 和 overwrite 属性。然而,一个关键的区别在于,使用 <enricher> 时,overwrite 属性默认值为 true,以与 <enricher> 元素的 <property> 子元素保持一致。从版本 4 开始。1, 您也可以指定一个可选的 null-result-expression 属性。当 enricher 返回 null 时,会对其进行求值,并返回求值的输出结果。 |
示例
本节包含在各种情况下使用负载增强器的多个示例。
| 此处显示的代码示例属于 Spring Integration Samples 项目。 请参阅 Spring Integration Samples。 |
在以下示例中,User 对象作为 Message 的负载被传递:
<int:enricher id="findUserEnricher"
input-channel="findUserEnricherChannel"
request-channel="findUserServiceChannel">
<int:property name="email" expression="payload.email"/>
<int:property name="password" expression="payload.password"/>
</int:enricher>
The User 具有多个属性,但仅初始设置了 username。
enricher 的 request-channel 属性被配置为将 User 传递给 findUserServiceChannel。
通过隐式设置的 reply-channel,返回一个 User 对象,并使用 property 子元素从回复中提取属性,以丰富原始负载。
如何将部分数据传递给请求通道?
当使用 request-payload-expression 属性时,可以将有效载荷的单个属性(而非完整消息)传递到请求通道。
在以下示例中,username 属性被传递到请求通道:
<int:enricher id="findUserByUsernameEnricher"
input-channel="findUserByUsernameEnricherChannel"
request-channel="findUserByUsernameServiceChannel"
request-payload-expression="payload.username">
<int:property name="email" expression="payload.email"/>
<int:property name="password" expression="payload.password"/>
</int:enricher>
请记住,尽管只传递了用户名,但发送到请求通道的结果消息包含完整的MessageHeaders集合。
如何丰富由集合数据组成的有效负载?
在以下示例中,传入的不是 User 对象,而是 Map:
<int:enricher id="findUserWithMapEnricher"
input-channel="findUserWithMapEnricherChannel"
request-channel="findUserByUsernameServiceChannel"
request-payload-expression="payload.username">
<int:property name="user" expression="payload"/>
</int:enricher>
Map 中的内容包含在 username 映射键下的用户名。
只有 username 被传递到请求通道。
回复中包含完整的 User 对象,该对象最终被添加到 Map 下的 user 键中。
如何在不使用请求通道的情况下用静态信息丰富负载?
以下示例完全不使用请求通道,而仅用静态值丰富消息的负载:
<int:enricher id="userEnricher"
input-channel="input">
<int:property name="user.updateDate" expression="new java.util.Date()"/>
<int:property name="user.firstName" value="William"/>
<int:property name="user.lastName" value="Shakespeare"/>
<int:property name="user.age" value="42"/>
</int:enricher>
请注意,此处对“static”一词的使用较为宽松。 您仍然可以使用 SpEL 表达式来设置这些值。