内容增强器
有时,您可能需要使用目标系统提供的以外的更多信息来增强请求。数据增强器模式描述了各种场景以及允许您解决此类需求的组件(增强器)。
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(例如errorChannel
、correlationId
、priority
、replyChannel
、routing-slip
等),您可以使用便捷的子元素直接设置这些值,而不是使用通用的<header>
子元素(您必须同时提供 Header 的“名称”和“值”)。
从 4.1 版开始,Header 增强器提供了一个routing-slip
子元素。请参阅路由单了解更多信息。
POJO 支持
通常,Header 值不能静态定义,必须根据消息中的某些内容动态确定。这就是为什么 Header 增强器允许您使用ref
和method
属性指定 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/>
。它没有属性。这个新的子元素将现有的replyChannel
和errorChannel
Header(当它们是MessageChannel
时)转换为String
,并将通道存储在注册表中以便稍后解析,即在发送回复或处理错误时。这对于 Header 可能丢失的情况非常有用,例如,将消息序列化到消息存储中或通过 JMS 传输消息时。如果 Header 不存在,或者它不是MessageChannel
,则不会进行任何更改。
使用此功能需要存在HeaderChannelRegistry
Bean。默认情况下,框架使用默认到期时间(60 秒)创建一个DefaultHeaderChannelRegistry
。通道会在此时间后从注册表中删除。要更改此行为,请使用integrationHeaderChannelRegistry
的id
定义一个 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,它可以是EventDrivenConsumer 或PollingConsumer 。可选。 |
4 | 当此端点连接为通道的订阅者时,调用顺序的指定。当该通道使用“故障转移”调度策略时,这一点尤其重要。当此端点本身是具有队列的通道的轮询使用者时,它不起作用。可选。 |
5 | 标识消息通道,消息在此端点处理后将发送到该通道。可选。 |
6 | 默认情况下,原始消息的 Payload 用作发送到request-channel 的 Payload。通过将 SpEL 表达式指定为request-payload-expression 属性的值,您可以使用原始 Payload 的子集、Header 值或任何其他可解析的 SpEL 表达式作为发送到request-channel 的 Payload 的基础。对于表达式求值,完整的 Message 可作为“根对象”使用。例如,以下 SpEL 表达式(以及其他表达式)是可能的:payload.something 、headers.something 、new 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 属性提供属性的名称。该属性应该可以在目标有效负载实例上设置。还必须提供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
对象,最终在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表达式来设置这些值。