内容丰富器
有时,您可能需要使用比目标系统提供的更多信息来增强请求。数据丰富器模式描述了各种场景以及使您能够满足此类需求的组件(丰富器)。
Spring Integration 的 Core
模块包含两个富化器:
它还包括三个适配器特定的标题增强器:
请参阅本参考手册的适配器特定部分,以了解更多关于这些适配器的信息。
有关表达式支持的更多信息,请参见 Spring 表达式语言 (SpEL)。
Header Enricher
如果你只需要为消息添加头信息,并且这些头信息不是根据消息内容动态确定的,那么引用一个自定义的转换器实现可能过于复杂。因此,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>
子元素,在通用的子元素中你需要同时提供头的 'name' 和 'value'。
从 4.1 版开始,header enricher 提供了一个 routing-slip
子元素。更多信息,请参见 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 配置用于 header enricher:
@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。默认情况下,框架会创建一个 DefaultHeaderChannelRegistry
,其默认过期时间为 60 秒。通道在此时间后从注册表中删除。要更改此行为,可以定义一个 id
为 integrationHeaderChannelRegistry
的 bean,并通过构造函数参数(以毫秒为单位)配置所需的默认延迟。
从 4.1 版本开始,你可以在 <bean/>
定义中将一个名为 removeOnGet
的属性设置为 true
,映射条目会在第一次使用时立即被移除。这在高流量环境下可能很有用,而且当通道只使用一次时,就不必等待清理程序来移除它。
HeaderChannelRegistry
有一个 size()
方法来确定注册表的当前大小。runReaper()
方法取消当前计划的任务并立即运行 reaper。然后根据当前的延迟再次计划任务。这些方法可以通过获取注册表的引用直接调用,或者你可以发送一条消息到控制总线,例如,以下内容的消息:
"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运算符(如果不存在标题则使用两分钟)。
Payload Enricher
负载增强器
在某些情况下,前面讨论的标题丰富器可能不够用,有效负载本身可能必须用额外的信息进行丰富。例如,进入 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>
发送消息以获取用于丰富数据的通道。可选。
生命周期属性,指示在应用程序上下文启动期间是否应启动此组件。默认为 true。可选。
底层 bean 定义的 ID,可以是
EventDrivenConsumer
或PollingConsumer
。可选。指定当此端点作为订阅者连接到通道时的调用顺序。当该通道使用“故障转移”调度策略时,这尤其相关。当此端点本身是具有队列的通道的轮询消费者时,它没有效果。可选。
标识消息在被此端点处理后发送到的消息通道。可选。
默认情况下,原始消息的有效负载用作发送到
request-channel
的有效负载。通过将 SpEL 表达式指定为request-payload-expression
属性的值,您可以使用原始有效负载的子集、头值或任何其他可解析的 SpEL 表达式作为发送到请求通道的有效负载的基础。对于表达式求值,整个消息可用作 '根对象'。例如,以下 SpEL 表达式(以及其他)是可能的:payload.something
,headers.something
,new java.util.Date()
,'thing1' + 'thing2'
期望回复消息的通道。这是可选的。通常,自动生成的临时回复通道就足够了。可选。
如果
request-channel
下游发生Exception
,则发送ErrorMessage
的通道。这使您能够返回一个替代对象以用于丰富。如果没有设置,Exception
将抛给调用者。可选。在向通道发送消息时等待的最大时间(以毫秒为单位),如果通道可能会阻塞。例如,当队列通道达到最大容量时,会一直阻塞直到有可用空间。内部地,
send()
超时设置在MessagingTemplate
上,并最终在调用MessageChannel
上的发送操作时应用。默认情况下,send()
超时设置为 '30'。可选。布尔值,指示是否应在发送消息到请求通道以获取丰富数据之前克隆实现
Cloneable
的任何有效负载。克隆版本将用作最终回复的目标有效负载。默认值为false
。可选。如果此端点是轮询消费者,则允许您配置消息轮询器。可选。
每个
property
子元素提供属性的名称(通过必需的name
属性)。该属性应在目标有效负载实例上可设置。必须提供value
或expression
属性之一 — 前者用于设置字面值,后者用于评估 SpEL 表达式。评估上下文的根对象是由此增强器发起的流程返回的消息 — 如果没有请求通道则是输入消息,或者应用程序上下文(使用@<beanName>.<beanProperty>
SpEL 语法)。从 4.0 版开始,当指定value
属性时,您还可以指定一个可选的type
属性。当目标是一个类型化的 setter 方法时,框架会适当地强制转换值(只要存在PropertyEditor
处理转换)。然而,如果目标有效负载是Map
,条目将填充值而无需转换。type
属性让您,例如,将包含数字的String
转换为目标有效负载中的Integer
值。从 4.1 版开始,您还可以指定一个可选的null-result-expression
属性。当enricher
返回 null 时,它会被评估,评估的结果将被返回。每个
header
子元素提供消息头的名称(通过必需的name
属性)。必须提供value
或expression
属性之一 — 前者用于设置字面值,后者用于评估 SpEL 表达式。评估上下文的根对象是由此增强器发起的流程返回的消息 — 如果没有请求通道则是输入消息,或者应用程序上下文(使用 '@<beanName>.<beanProperty>' 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>
User
有几个属性,但最初只设置了 username
。富化器的 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
集。
我如何丰富由集合数据组成的有效负载?
在以下示例中,传递的是一个 Map
,而不是一个 User
对象:
<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
map 键下包含用户名。只有 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 表达式来设置这些值。