内容增强器

有时,您可能需要使用目标系统提供的以外的更多信息来增强请求。数据增强器模式描述了各种场景以及允许您解决此类需求的组件(增强器)。

Spring Integration 的Core模块包含两个增强器

它还包含三个特定于适配器的 Header 增强器

请参阅本参考手册中特定于适配器的部分,以了解有关这些适配器的更多信息。

有关表达式支持的更多信息,请参阅Spring 表达式语言 (SpEL)

Header 增强器

如果您只需要向消息添加 Header,并且 Header 不是由消息内容动态确定的,那么引用转换器的自定义实现可能显得过于复杂。出于这个原因,Spring Integration 提供了对 Header 增强器模式的支持。它通过<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>

Header 增强器还提供有用的子元素来设置众所周知的 Header 名称,如下例所示

<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>

前面的配置表明,对于众所周知的 Header(例如errorChannelcorrelationIdpriorityreplyChannelrouting-slip等),您可以使用便捷的子元素直接设置这些值,而不是使用通用的<header>子元素(您必须同时提供 Header 的“名称”和“值”)。

从 4.1 版开始,Header 增强器提供了一个routing-slip子元素。请参阅路由单了解更多信息。

POJO 支持

通常,Header 值不能静态定义,必须根据消息中的某些内容动态确定。这就是为什么 Header 增强器允许您使用refmethod属性指定 Bean 引用。指定的方法计算 Header 值。考虑以下配置和一个修改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) 的便利性,以帮助配置许多不同的组件。Header 增强器就是其中之一。再来看一下前面显示的 POJO 示例。您可以看到,确定 Header 值的计算逻辑非常简单。一个自然的问题是:“有没有更简单的方法来实现这一点?”这就是 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 配置配置 Header 增强器

以下两个示例显示了如何使用 Java 配置进行 Header 增强。

@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。

使用 Java DSL 配置 Header 增强器

以下示例显示了 Header 增强器的 Java DSL 配置

@Bean
public IntegrationFlow enrichHeadersInFlow() {
    return f -> f
                ...
                .enrichHeaders(h -> h.header("emailUrl", this.emailUrl)
                                     .headerExpression("from", "payload.from[0].toString()"))
                .handle(...);
}

Header 通道注册表

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

使用此功能需要存在HeaderChannelRegistry Bean。默认情况下,框架使用默认到期时间(60 秒)创建一个DefaultHeaderChannelRegistry。通道会在此时间后从注册表中删除。要更改此行为,请使用integrationHeaderChannelRegistryid定义一个 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 增强器

在某些情况下,前面讨论的 Header 增强器可能不够用,并且 Payload 本身可能必须用其他信息来丰富。例如,进入 Spring Integration 消息系统的订单消息必须根据提供的客户编号查找订单的客户,然后用该信息丰富原始 Payload。

Spring Integration 2.1 引入了 Payload 增强器。Payload 增强器定义一个端点,该端点将Message传递到公开的请求通道,然后期望一个回复消息。然后,回复消息成为用于评估表达式以丰富目标 Payload 的根对象。

Payload 增强器通过enricher元素提供完整的 XML 命名空间支持。为了发送请求消息,Payload 增强器具有一个request-channel属性,允许您将消息调度到请求通道。

基本上,通过定义请求通道,Payload 增强器充当网关,等待发送到请求通道的消息返回。然后,增强器使用回复消息提供的数据来增强消息的 Payload。

将消息发送到请求通道时,您还可以选择使用request-payload-expression属性仅发送原始 Payload 的子集。

Payload 的丰富是通过 SpEL 表达式配置的,提供了最大程度的灵活性。因此,您不仅可以使用回复通道的Message中的直接值来丰富 Payload,还可以使用 SpEL 表达式从该消息中提取子集或应用额外的内联转换,从而进一步操作数据。

如果您只需要使用静态值来丰富 Payload,则不需要提供request-channel属性。

增强器是转换器的变体。在许多情况下,您可以使用 Payload 增强器或通用转换器实现向消息 Payload 添加其他数据。您应该熟悉 Spring Integration 提供的所有能够进行转换的组件,并仔细选择语义上最符合您的业务案例的实现。

配置

以下示例显示了 Payload 增强器的所有可用配置选项

<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,它可以是EventDrivenConsumerPollingConsumer。可选。
4 当此端点连接为通道的订阅者时,调用顺序的指定。当该通道使用“故障转移”调度策略时,这一点尤其重要。当此端点本身是具有队列的通道的轮询使用者时,它不起作用。可选。
5 标识消息通道,消息在此端点处理后将发送到该通道。可选。
6 默认情况下,原始消息的 Payload 用作发送到request-channel的 Payload。通过将 SpEL 表达式指定为request-payload-expression属性的值,您可以使用原始 Payload 的子集、Header 值或任何其他可解析的 SpEL 表达式作为发送到request-channel的 Payload 的基础。对于表达式求值,完整的 Message 可作为“根对象”使用。例如,以下 SpEL 表达式(以及其他表达式)是可能的:payload.somethingheaders.somethingnew 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属性提供属性的名称。该属性应该可以在目标有效负载实例上设置。还必须提供valueexpression属性中的一个——前者用于设置字面值,后者用于评估SpEL表达式。评估上下文的根对象是从此丰富器启动的流返回的消息——如果没有请求通道,则为输入消息;或者为应用程序上下文(使用@<beanName>.<beanProperty> SpEL语法)。从4.0版开始,在指定value属性时,还可以指定可选的type属性。当目标是类型化的setter方法时,框架会适当地强制转换值(只要存在PropertyEditor来处理转换)。但是,如果目标有效负载是Map,则会在不进行转换的情况下使用该值填充条目。例如,type属性允许您将包含数字的String转换为目标有效负载中的Integer值。从4.1版开始,您还可以指定可选的null-result-expression属性。当enricher返回null时,它将被评估,并返回评估的结果。
13 每个header子元素都通过必需的name属性提供消息头的名称。还必须提供valueexpression属性中的一个——前者用于设置字面值,后者用于评估SpEL表达式。评估上下文的根对象是从此丰富器启动的流返回的消息——如果没有请求通道,则为输入消息;或者为应用程序上下文(使用'@<beanName>.<beanProperty>' SpEL语法)。请注意,与<header-enricher>类似,<enricher>元素的header元素具有typeoverwrite属性。但是,一个关键的区别是,对于<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>

Mapusername映射键下包含用户名。只有username被传递到请求通道。回复包含一个完整的User对象,最终在user键下添加到Map中。

如何不使用请求通道而使用静态信息丰富有效负载?

下面的示例根本不使用请求通道,而是仅使用静态值丰富消息的有效负载。

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