内容丰富器
有时,你可能需要用比目标系统提供的信息更多的信息来增强请求。 数据丰富器模式描述了各种场景以及让你满足此类需求的组件(丰富器)。
Spring Integration Core 模块包含两个丰富器
它还包括三个适配器特定的消息头丰富器
请参阅本参考手册中适配器特定部分以了解有关这些适配器的更多信息。
有关表达式支持的更多信息,请参阅 Spring Expression Language (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 Expression Language (SpEL) 的便利性,以帮助配置许多不同的组件。消息头丰富器就是其中之一。再次查看前面所示的 POJO 示例。您可以看到确定消息头值的计算逻辑非常简单。一个自然的问题是:“有没有更简单的方法来实现这一点?”。这就是 SpEL 真正强大之处。考虑以下示例
<int:header-enricher input-channel="in" output-channel="out">
<int:header name="foo" expression="payload.toUpperCase() + '_US'"/>
</int:header-enricher>
通过在这些简单情况下使用 SpEL,您不再需要提供单独的类并在应用程序上下文中配置它。您所需要做的就是使用有效的 SpEL 表达式配置 expression 属性。“payload”和“headers”变量被绑定到 SpEL 评估上下文,让您完全访问传入消息。
使用 Java 配置配置消息头丰富器
以下两个示例展示了如何为消息头丰富器使用 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 配置消息头丰富器
以下示例展示了消息头丰富器的 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 的消息体。通过将 SpEL 表达式指定为 request-payload-expression 属性的值,您可以使用原始消息体的一个子集、一个消息头值或任何其他可解析的 SpEL 表达式作为发送到 request-channel 的消息体的基础。对于表达式评估,完整消息作为“根对象”可用。例如,以下 SpEL 表达式(以及其他)是可能的:payload.something、headers.something、new java.util.Date()、'thing1' + 'thing2' |
| 7 | 期望回复消息的通道。这是可选的。通常,自动生成的临时回复通道就足够了。可选。 |
| 8 | 如果 request-channel 的下游发生 Exception,则发送 ErrorMessage 的通道。这使您可以返回一个用于丰富替代对象。如果未设置,则向调用方抛出 Exception。可选。 |
| 9 | 如果通道可能阻塞,则发送消息到通道时等待的最大毫秒数。例如,如果队列通道达到其最大容量,它可能会阻塞直到有空间可用。在内部,send() 超时在 MessagingTemplate 上设置,并最终在调用 MessageChannel 上的 send 操作时应用。默认情况下,send() 超时设置为“30”。可选。 |
| 10 | 布尔值,指示任何实现 Cloneable 的消息体是否应在将消息发送到请求通道以获取丰富数据之前进行克隆。克隆版本将用作最终回复的目标消息体。默认值为 false。可选。 |
| 11 | 如果此端点是轮询消费者,则允许您配置消息轮询器。可选。 |
| 12 | 每个 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 时,将评估该表达式,并返回评估结果。 |
| 13 | 每个 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 属性时,可以将消息体的单个属性而不是完整消息传递到请求通道。在以下示例中,用户名属性被传递到请求通道
<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 映射键下的用户名。只有 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>
请注意,这里的“静态”一词使用得比较宽松。您仍然可以使用 SpEL 表达式来设置这些值。