跳到主要内容
版本:7.0.2

内容增强器

DeepSeek V3 中英对照 Content 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 子元素。更多信息请参阅路由条

POJO 支持

通常,标头值无法静态定义,必须根据消息中的某些内容动态确定。因此,标头增强器还允许您使用 refmethod 属性来指定一个 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处理这类简单场景,您不再需要提供单独的类并在应用上下文中进行配置。您只需使用有效的SpEL表达式配置expression属性即可。'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/>。该元素没有属性。这个新的子元素会将现有的 replyChannelerrorChannel 头信息(当它们是 MessageChannel 类型时)转换为 String,并将通道存储在注册表中,以便在需要发送回复或处理错误时进行后续解析。这在某些情况下非常有用,例如当消息头可能丢失时——比如将消息序列化到消息存储中,或者通过 JMS 传输消息时。如果头信息不存在,或者它不是 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>

第一种情况下,每个头部通道映射的存活时间均为两分钟。第二种情况下,存活时间在消息头部中指定,并使用 Elvis 操作符在无头部时默认采用两分钟。

载荷增强器

在某些情况下,如前所述,仅使用头部增强器可能不够充分,必须对负载本身进行额外信息的增强。例如,进入 Spring Integration 消息系统的订单消息需要根据提供的客户编号查找对应的客户信息,然后将这些信息添加到原始负载中。

Spring Integration 2.1 引入了有效负载丰富器。有效负载丰富器定义了一个端点,该端点将 Message 传递到暴露的请求通道,然后期望一个回复消息。该回复消息随后成为评估表达式以丰富目标有效负载的根对象。

负载丰富器通过 enricher 元素提供完整的 XML 命名空间支持。为了发送请求消息,负载丰富器具有 request-channel 属性,允许您将消息分派到请求通道。

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

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

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

如果仅需使用静态值来丰富载荷,则无需提供 request-channel 属性。

备注

Enricher(增强器)是 transformer(转换器)的一种变体。在许多情况下,你可以使用 payload enricher(负载增强器)或通用的 transformer 实现来向消息负载添加额外数据。你应该熟悉 Spring Integration 提供的所有具备转换功能的组件,并仔细选择在语义上最符合你业务场景的实现。

配置

以下示例展示了 payload enricher 的所有可用配置选项:

<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,该定义可以是 EventDrivenConsumerPollingConsumer。可选。

  • 指定当此端点作为订阅者连接到通道时的调用顺序。当该通道使用“故障转移”调度策略时,这一点尤其重要。当此端点本身是带有队列的通道的轮询消费者时,此属性无效。可选。

  • 标识消息在此端点处理后发送到的消息通道。可选。

  • 默认情况下,原始消息的有效负载被用作发送到 request-channel 的有效负载。通过为 request-payload-expression 属性指定一个 SpEL 表达式作为值,您可以使用原始有效负载的子集、标头值或任何其他可解析的 SpEL 表达式作为发送到请求通道的有效负载的基础。对于表达式求值,完整消息作为“根对象”可用。例如,以下 SpEL 表达式(以及其他)是可能的:payload.somethingheaders.somethingnew java.util.Date()'thing1' + 'thing2'

  • 期望回复消息的通道。这是可选的。通常,自动生成的临时回复通道就足够了。可选。

  • 如果在 request-channel 下游发生 Exception,则 ErrorMessage 被发送到的通道。这使您可以返回一个替代对象用于增强。如果未设置,则会将 Exception 抛出给调用者。可选。

  • 当向通道发送消息时,如果通道可能阻塞,则等待的最长时间(以毫秒为单位)。例如,如果队列通道已达到其最大容量,则它可能会阻塞直到有可用空间。在内部,send() 超时设置在 MessagingTemplate 上,并最终在 MessageChannel 上调用发送操作时应用。默认情况下,send() 超时设置为 '30'。可选。

  • 布尔值,指示在将消息发送到请求通道以获取增强数据之前,是否应克隆任何实现 Cloneable 的有效负载。克隆的版本将用作最终回复的目标有效负载。默认值为 false。可选。

  • 如果此端点是轮询消费者,则允许您配置消息轮询器。可选。

  • 每个 property 子元素提供一个属性的名称(通过必需的 name 属性)。该属性应该是目标有效负载实例上的可设置属性。还必须提供 valueexpression 属性中的一个——前者用于设置字面值,后者用于要计算的 SpEL 表达式。求值上下文的根对象是从此增强器启动的流返回的消息——如果没有请求通道,则是输入消息,或者是应用程序上下文(使用 @<beanName>.<beanProperty> SpEL 语法)。从版本 4.0 开始,当指定 value 属性时,您还可以指定一个可选的 type 属性。当目标是类型化的 setter 方法时,框架会适当地强制转换该值(只要存在 PropertyEditor 来处理转换)。但是,如果目标有效负载是 Map,则条目将直接填充该值而无需转换。例如,type 属性允许您将包含数字的 String 转换为目标有效负载中的 Integer 值。从版本 4.1 开始,您还可以指定一个可选的 null-result-expression 属性。当 enricher 返回 null 时,将对其进行求值,并返回求值的输出。

  • 每个 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。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 对象,该对象最终被添加到 Mapuser 键下。

如何在不使用请求通道的情况下使用静态信息丰富载荷?

以下示例完全不使用请求通道,仅通过静态值来丰富消息的有效载荷:

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