服务激活器
服务激活器是将任何 Spring 管理的对象连接到输入通道的端点类型,使其能够扮演服务的角色。如果服务产生输出,它也可以连接到输出通道。另外,一个产生输出的服务可以位于处理管道或消息流的末端,在这种情况下可以使用入站消息的 replyChannel
消息头。如果未定义输出通道,则这是默认行为。与此处描述的大多数配置选项一样,相同的行为实际上适用于大多数其他组件。
服务激活器本质上是一个通用端点,用于使用输入消息(载荷和消息头)调用某个对象上的方法。其内部逻辑基于 MessageHandler
,它可以是针对特定用例的任何可能的实现,例如 DefaultMessageSplitter
、AggregatingMessageHandler
、SftpMessageHandler
、JpaOutboundGateway
等。因此,本参考手册中提到的任何出站网关和出站通道适配器都应视为此服务激活器端点的特定扩展;它们最终都调用了某个对象的方法。
配置服务激活器
使用 Java 和注解配置时,只需使用 @ServiceActivator
注解标记相应的方法,框架在消息从输入通道消费时会调用它。
public class SomeService {
@ServiceActivator(inputChannel = "exampleChannel")
public void exampleHandler(SomeData payload) {
...
}
}
更多信息请参见 注解支持。
对于 Java、Groovy 或 Kotlin DSL,IntegrationFlow
的 .handle()
操作符代表一个服务激活器。
-
Java DSL
-
Kotlin DSL
-
Groovy DSL
@Bean
public IntegrationFlow someFlow() {
return IntegrationFlow
.from("exampleChannel")
.handle(someService, "exampleHandler")
.get();
}
@Bean
fun someFlow() =
integrationFlow("exampleChannel") {
handle(someService, "exampleHandler")
}
@Bean
someFlow() {
integrationFlow 'exampleChannel',
{
handle someService, 'exampleHandler'
}
}
更多关于 DSL 的信息请参见相应的章节。
使用 XML 配置创建服务激活器时,使用 'service-activator' 元素并带有 'input-channel' 和 'ref' 属性,如下例所示。
<int:service-activator input-channel="exampleChannel" ref="exampleHandler"/>
上述配置选择了 exampleHandler
中符合以下消息传递要求的任何方法:
-
使用
@ServiceActivator
注解标记 -
是
public
-
如果
requiresReply == true
则不返回void
运行时要调用的目标方法是为每个请求消息根据其 payload
类型选择的,如果目标类上存在一个 Message<?>
类型的方法,则会回退到该方法。
从版本 5.0 开始,一个服务方法可以使用 @org.springframework.integration.annotation.Default
进行标记,作为所有不匹配情况的备用。当使用 内容类型转换 并且目标方法在转换后被调用时,这会很有用。
要委托给任何对象的显式定义方法,可以添加 method
属性,如下例所示:
<int:service-activator input-channel="exampleChannel" ref="somePojo" method="someMethod"/>
在任何一种情况下,当服务方法返回非 null 值时,端点会尝试将回复消息发送到适当的回复通道。为了确定回复通道,它首先检查端点配置中是否提供了 output-channel
,如下例所示:
<int:service-activator input-channel="exampleChannel" output-channel="replyChannel"
ref="somePojo" method="someMethod"/>
如果方法返回结果并且未定义 output-channel
,框架会检查请求消息的 replyChannel
消息头的值。如果该值可用,它会检查其类型。如果它是 MessageChannel
,回复消息会发送到该通道。如果它是一个 String
,端点会尝试将通道名称解析为通道实例。如果通道无法解析,会抛出 DestinationResolutionException
。如果可以解析,消息会被发送到那里。如果请求消息没有 replyChannel
消息头且 reply
对象是一个 Message
,则会查阅其 replyChannel
消息头以确定目标地址。这是 Spring Integration 中用于请求-回复消息传递的技术,它也是返回地址模式的一个例子。
如果你的方法返回结果,并且你希望丢弃它并结束流程,你应该将 output-channel
配置为发送到 NullChannel
。为了方便,框架会注册一个名为 nullChannel
的通道。更多信息请参见 特殊通道。
服务激活器是那些不需要产生回复消息的组件之一。如果你的方法返回 null
或具有 void
返回类型,则服务激活器在方法调用后退出,不发送任何信号。此行为可以通过 AbstractReplyProducingMessageHandler.requiresReply
选项控制,该选项在使用 XML namespace 配置时也暴露为 requires-reply
。如果该标志设置为 true
且方法返回 null,则会抛出 ReplyRequiredException
。
服务方法中的参数可以是消息或任意类型。如果是后者,则假定它是消息载荷,该载荷会从消息中提取并注入到服务方法中。我们通常推荐这种方法,因为它遵循并推广了在使用 Spring Integration 时的 POJO 模型。参数还可以带有 @Header
或 @Headers
注解,如 注解支持 中所述。
服务方法不需要有任何参数,这意味着你可以实现事件风格的服务激活器(你只需要关注服务方法的调用),而无需担心消息的内容。可以将其视为一个 null JMS 消息。这种实现的示例用例是一个简单的计数器或监视器,用于监控输入通道上接收到的消息数量。 |
从版本 4.1 开始,框架会正确地将消息属性(payload
和 headers
)转换为 Java 8 的 Optional
POJO 方法参数,如下例所示:
public class MyBean {
public String computeValue(Optional<String> payload,
@Header(value="foo", required=false) String foo1,
@Header(value="foo") Optional<String> foo2) {
if (payload.isPresent()) {
String value = payload.get();
...
}
else {
...
}
}
}
如果自定义服务激活器处理程序实现可以在其他 <service-activator>
定义中重用,我们通常建议使用 ref
属性。但是,如果自定义服务激活器处理程序实现仅在单个 <service-activator>
定义中使用,你可以提供一个内部 bean 定义,如下例所示:
<int:service-activator id="exampleServiceActivator" input-channel="inChannel"
output-channel = "outChannel" method="someMethod">
<beans:bean class="org.something.ExampleServiceActivator"/>
</int:service-activator>
在同一个 <service-activator> 配置中同时使用 ref 属性和内部处理程序定义是不允许的,因为它会创建歧义条件并导致抛出异常。 |
如果 ref 属性引用了一个扩展 AbstractMessageProducingHandler 的 bean(例如框架本身提供的处理程序),则通过将输出通道直接注入到处理程序中来优化配置。在这种情况下,每个 ref 都必须引用一个独立的 bean 实例(或 prototype 作用域的 bean),或者使用内部 <bean/> 配置类型。如果意外地从多个 bean 引用了同一个消息处理程序,则会收到配置异常。 |
服务激活器与 Spring Expression Language (SpEL)
自 Spring Integration 2.0 起,服务激活器也可以利用 SpEL。
例如,你可以调用任何 bean 方法,而无需在 ref
属性中指向 bean 或将其作为内部 bean 定义包含在内,如下所示:
<int:service-activator input-channel="in" output-channel="out"
expression="@accountService.processAccount(payload, headers.accountId)"/>
<bean id="accountService" class="thing1.thing2.Account"/>
在上述配置中,我们没有使用 ref
或内部 bean 注入 'accountService',而是使用了 SpEL 的 @beanId
标记并调用了一个接受与消息载荷兼容类型的方法。我们还传递了一个消息头值。任何有效的 SpEL 表达式都可以针对消息中的任何内容进行评估。对于简单场景,如果所有逻辑都可以封装在这样的表达式中,你的服务激活器无需引用 bean,如下例所示:
<int:service-activator input-channel="in" output-channel="out" expression="payload * 2"/>
在上述配置中,我们的服务逻辑是将载荷值乘以二。SpEL 让我们能够相对轻松地处理它。
更多关于配置服务激活器的信息,请参见 Java DSL 章中的 服务激活器与 .handle() 方法。
异步服务激活器
服务激活器由调用线程调用。如果输入通道是 SubscribableChannel
,这就是上游线程;如果是 PollableChannel
,这就是轮询线程。如果服务返回 CompletableFuture<?>
,默认操作是将它作为发送到输出(或回复)通道的消息的载荷。从版本 4.3 开始,你现在可以将 async
属性设置为 true
(使用 Java 配置时通过 setAsync(true)
)。如果当 async
属性设置为 true
时服务返回 CompletableFuture<?>
,则调用线程会立即释放,回复消息会在完成 future 的线程(在你服务内部)上发送。这对于使用 PollableChannel
的长时间运行的服务特别有利,因为轮询线程被释放,可以在框架内执行其他服务。
如果服务以 Exception
完成 future,则会发生正常的错误处理。ErrorMessage
会发送到 errorChannel
消息头(如果存在)。否则,ErrorMessage
会发送到默认的 errorChannel
(如果可用)。
从版本 6.1 开始,如果 AbstractMessageProducingHandler
的输出通道配置为 ReactiveStreamsSubscribableChannel
,则默认启用异步模式。如果处理程序结果不是响应式类型或 CompletableFuture<?>
,则无论输出通道类型如何,都会发生常规的回复生成过程。
另请参见 Reactive Streams 支持 以获取更多信息。
服务激活器和方法返回类型
服务方法可以返回任何类型,该类型将成为回复消息的载荷。在这种情况下,会创建一个新的 Message<?>
对象,并复制请求消息中的所有消息头。这对于大多数 Spring Integration 的 MessageHandler
实现以相同方式工作,当交互基于 POJO 方法调用时。
方法也可以返回完整的 Message<?>
对象。但是,请记住,与 转换器 不同,对于服务激活器,如果回复消息中不存在请求消息中的消息头,则该消息会通过复制请求消息中的消息头进行修改。因此,如果你的方法参数是 Message<?>
并且你在服务方法中复制了一些(但不是全部)现有消息头,它们会重新出现在回复消息中。服务激活器不负责从回复消息中删除消息头,并且为了遵循松耦合原则,最好在集成流中添加 HeaderFilter
。另外,可以使用转换器代替服务激活器,但在那种情况下,当返回完整的 Message<?>
时,方法完全负责该消息,包括复制请求消息头(如果需要)。你必须确保重要框架消息头(例如 replyChannel
、errorChannel
),如果存在,必须得到保留。