Content Enricher

  • 标头增强器:允许向消息添加新标头或动态设置现有标头。

  • 有效负载增强器:将消息传递到一个请求通道,接收一个回复,并将回复中的数据用于扩充原始有效负载。

这些增强器可以通过 XML 名称空间或 Java 配置进行配置,并支持 SpEL 表达式、POJO 和 Groovy 脚本。

有时,您可能需要使用比目标系统提供的更多信息来增强请求。 data enricher模式描述了各种场景以及组件(Enricher),该组件可让您满足此类要求。 Spring Integration“Core”模块包括两个增强器:

它还包括三个特定于适配器的标头增强器:

请参阅本参考手册的特定于适配器的部分,以了解有关这些适配器的更多信息。 有关表达式支持的更多信息,请参阅 Spring Expression Language (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>

前面的配置显示,对于知名的头部(例如 errorChannelcorrelationIdpriorityreplyChannelrouting-slip 等),无需使用通用的 <header> 子元素(在其中需同时提供头部的“名称”和“值”),可使用便利的子元素直接设置这些值。

从版本 4.1 开始,头增强器提供 routing-slip 子元素。有关详细信息,请参阅 Routing Slip

POJO Support

通常,头部值无法静态定义,而必须基于消息中一些内容动态确定。这就是 header enricher 允许通过使用 refmethod 属性指定 bean 引用。指定的 method 计算头部值。考虑以下配置和一个通过 method 修改 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 Support

在 Spring 集成 2.0 中,我们引入了 @{1} 的便利性,以帮助配置许多不同的组件。标头强化器就是其中之一。再看看前面所示的 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 属性。payloadheaders 变量绑定到 SpEL 评估上下文中,让您完全访问传入的消息。

Configuring a Header Enricher with Java Configuration

以下两个示例演示如何对 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;
}

第一个示例添加了单个文字 header。第二个示例添加了两个 header,一个文字 header 和一个基于 SpEL 表达式的 header。

Configuring a Header Enricher with the 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(...);
}

Header Channel Registry

从 Spring Integration 3.0 开始,新的子元素 <int:header-channels-to-string/> 可用。它没有属性。此新子元素将现有的 replyChannelerrorChannel header(当它们是 MessageChannel 时)转换为 String,并将通道存储到注册表中以备稍后解决,也就是需要发送回复或处理错误时。在 header 可能丢失的情况下,这个很有用 —— 例如,当将消息序列化到消息存储或通过 JMS 传输消息时。如果 header 尚未存在,或者它不是 MessageChannel,则不会进行任何更改。

使用此功能要求存在 HeaderChannelRegistry bean。默认情况下,框架创建带有默认过期时间(60 秒)的 DefaultHeaderChannelRegistry。此时间过后就从注册表中移除通道。要更改此行为,请使用构造函数参数(以毫秒为单位)定义一个 idintegrationHeaderChannelRegistry 的 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>

在第一种情况下,每个 header 通道映射的生存时间为两分钟。在第二种情况下,生存时间在消息 header 中指定,并利用 Elvis 运算符在没有 header 的情况下使用两分钟。

Payload Enricher

在某些情况下,前面讨论的 header enricher 可能不够,而 payload 本身可能需要使用其他信息来充实。例如,进入 Spring Integration 消息传送系统的订单消息必须根据提供的客户编号查找订单的客户,然后使用这些信息充实原始 payload。

Spring Integration 2.1 引入了 payload enricher。payload enricher 定义了一个端点,将 Message 传递到公开的请求通道,然后它预计收到一个回复消息。然后回复消息会成为评估表达式以充实目标 payload 的根对象。

有效负载增强器通过 enricher 元素提供完整的 XML 名称空间支持。为了发送请求消息,有效负载增强器有一个 request-channel 属性,便于将消息分派到请求通道。

基本上,通过定义请求通道,有效负载增强器充当网关,等待发送到请求通道的消息返回。然后,该增强器会根据回复消息提供的数据扩充消息的有效负载。

在向请求通道发送消息时,您还可以仅通过使用 request-payload-expression 属性发送原始有效负载的子集。

有效负载的扩充是通过 SpEL 表达式配置的,提供了最大程度的灵活性。因此,您不仅可以用来自回复通道的 Message 的直接值扩充有效负载,还可以使用 SpEL 表达式从该消息中提取子集或应用其他内联转换,从而进一步处理数据。

如果您只需要用静态值扩充有效负载,则无需提供 request-channel 属性。

丰富器是转换器的一种变体。在许多情况下,可以使用有效负载丰富器或通用转换器实现来向你的消息有效负载中添加额外数据。你应熟悉 Spring Integration 提供的所有转换功能组件,并仔细选择在语义上最适合你的业务案例的实现。

Configuration

以下示例显示了有效负载增强器的所有可用配置选项:

<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 指定为此端点作为通道的订阅者连接时的调用顺序。在该通道使用 “failover” 分派策略时,此属性尤为相关。当此端点本身是具有队列的通道轮询使用者时,它不会产生任何影响。可选。
5 标识处理此端点后发送消息的消息通道。可选。
6 默认情况下,原始消息的有效负载用作发送到 request-channel 的有效负载。通过将 SpEL 表达式指定为 request-payload-expression 特性的值,可以使用原始有效负载的子集、标头值或任何其他可解析的 SpEL 表达式作为发送到请求通道的有效负载基础。对于表达式求值,则可用完整消息作为“根对象”。例如,可以采用下列 SpEL 表达式(除其他表达式之外): payload.somethingheaders.somethingnew java.util.Date()、’thing1' + 'thing2'
7 预期收到回复消息的通道。此项为可选。通常,自动生成的临时回复通道就足够了。可选。
8 如果通道可能阻塞,则在将消息发送到通道时等待的最大时间(以毫秒为单位)。例如,当队列通道达到其最大容量时,队列通道可能会阻塞,直到有空间可用。在内部,send() 超时设置为 MessagingTemplate,最终在 MessageChannel 上调用发送操作时应用。默认情况下,send() 超时设置为“30”。可选。
9 布尔值,指示在将消息发送到请求通道以获取丰富数据之前,是否应克隆实现 Cloneable 的任何有效负载。克隆的版本将用作最终回复的目标有效负载。默认值为 false。可选。
10 如果此端点是轮询使用者,则它允许你配置消息轮询器。可选。
11 每个 property 子元素通过强制性的 name 特性提供属性的名称。此属性应可设置在目标有效负载实例上。还必须提供 valueexpression 特性(只能提供一个)——其中前者用于设置文本值,而后者用于求值 SpEL 表达式。求值上下文的根对象是此增强器启动的流返回的消息——如果不存在请求通道,则为输入消息,或者为应用程序上下文(使用 @&lt;beanName&gt;.&lt;beanProperty&gt; SpEL 语法)。从版本 4.0 开始,当指定 value 特性时,还可以指定可选的 type 特性。当目标是类型设置方法时,框架会适当强制转换值(只要存在 PropertyEditor 来处理转换)。但是,如果目标有效负载是 Map,则使用该值填充该条目,而无需转换。例如,type 特性允许你将包含数字的 String 转换为目标有效负载中的 Integer 值。从版本 4.1 开始,还可以指定可选的 null-result-expression 特性。当 enricher 返回 null 时,会对其进行求值,然后返回求值输出。
12 每个 header 子元素通过强制性的 name 特性提供消息标头的名称。还必须提供 valueexpression 特性(只能提供一个)——其中前者用于设置文本值,而后者用于求值 SpEL 表达式。求值上下文的根对象是此增强器启动的流返回的消息——如果不存在请求通道,则为输入消息,或者为应用程序上下文(使用“@<beanName>.<beanProperty>”SpEL 语法)。请注意,与 &lt;header-enricher&gt; 类似,&lt;enricher&gt; 元素的 header 元素具有 typeoverwrite 特性。但是,关键的不同之处在于,使用 &lt;enricher&gt; 时,overwrite 特性默认为 true,以便与 &lt;enricher&gt; 元素的 &lt;property&gt; 子元素保持一致。从版本 4.1 开始,还可以指定可选的 null-result-expression 特性。当 enricher 返回 null 时,会对其进行求值,然后返回求值输出。
13 WebSocketStompClient:建立在 Spring WebSocket API 上,支持标准 JSR-356 WebSocket、Jetty 9 和 SockJS(用于与 SockJS 客户端进行基于 HTTP 的 WebSocket 仿真)。

Examples

此部分包含有关在各种情况下使用有效负载增强器的几个示例。

这里显示的代码示例是 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 子元素,从回复中提取属性并用于扩充原始有效负载。

How Do I Pass Only a Subset of Data to the Request Channel?

在使用 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>

请记住,尽管只传递了 username,但发送到请求通道的结果消息包含全部 MessageHeaders

How Can I Enrich Payloads that Consist of Collection Data?

在以下示例中,不是 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 包含 map 键 username 下的 username。只有 username 被传递到请求通道。回复包含一个完整的 User 对象,最终该对象已添加到键 user 下的 Map 中。

How Can I Enrich Payloads with Static Information without Using a Request Channel?

以下示例根本不使用请求通道,而只用静态值扩充消息的有效负载:

<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 表达式来设置这些值。