服务激活器

服务激活器是将任何 Spring 管理的对象连接到输入通道的端点类型,以便它可以充当服务。如果服务产生输出,它也可以连接到输出通道。或者,产生输出的服务可以位于处理管道或消息流的末端,在这种情况下,可以使用入站消息的 replyChannel 标头。如果未定义输出通道,这是默认行为。与这里描述的大多数配置选项一样,相同行为实际上适用于大多数其他组件。

服务激活器本质上是一个通用的端点,用于使用输入消息(有效负载和标头)调用某个对象上的方法。它的内部逻辑基于一个 MessageHandler,它可以是特定用例的任何可能的实现,例如 DefaultMessageSplitterAggregatingMessageHandlerSftpMessageHandlerJpaOutboundGateway 等。因此,本参考手册中提到的任何出站网关和出站通道适配器都应视为此服务激活器端点的特定扩展;它们最终都调用某个对象的方法。

配置服务激活器

使用 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 配置创建服务激活器,请使用带有 input-channelref 属性的 service-activator 元素,如下例所示。

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

在这两种情况下,当服务方法返回非空值时,端点尝试将回复消息发送到适当的回复通道。为了确定回复通道,它首先检查端点配置中是否提供了 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 命名空间配置时也作为requires-reply公开。如果该标志设置为true并且方法返回 null,则会抛出ReplyRequiredException

服务方法中的参数可以是消息或任意类型。如果是后者,则假定它是一个消息有效负载,该有效负载从消息中提取并注入到服务方法中。我们通常建议使用这种方法,因为它在使用 Spring Integration 时遵循并促进了 POJO 模型。参数也可以具有@Header@Headers注释,如注释支持中所述。

服务方法不需要有任何参数,这意味着您可以实现事件风格的服务激活器(您只关心服务方法的调用),而不必担心消息的内容。将其视为一个空 JMS 消息。此类实现的一个示例用例是简单计数器或监控输入通道上存放的消息。

从 4.1 版开始,框架会正确地将消息属性(payloadheaders)转换为 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 {
           ...
       }
    }

}

通常建议使用 ref 属性,如果自定义服务激活器处理程序实现可以在其他 <service-activator> 定义中重复使用。但是,如果自定义服务激活器处理程序实现仅在 <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 表达式语言 (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"/>

在前面的配置中,我们使用 SpEL 的 @beanId 符号调用一个方法,该方法接受与消息有效负载类型兼容的类型,而不是使用 ref 或作为内部 bean 注入 'accountService'。我们还传递一个标头值。任何有效的 SpEL 表达式都可以针对消息中的任何内容进行评估。对于简单的场景,如果所有逻辑都可以封装在这样的表达式中,您的服务激活器不需要引用 bean,如下例所示

<int:service-activator input-channel="in" output-channel="out" expression="payload * 2"/>

在前面的配置中,我们的服务逻辑是将有效负载值乘以 2。SpEL 使我们能够相对轻松地处理它。

有关配置服务激活器的更多信息,请参阅 Java DSL 章节中的 服务激活器和 .handle() 方法

异步服务激活器

服务激活器由调用线程调用。如果输入通道是SubscribableChannel,则为上游线程;如果输入通道是PollableChannel,则为轮询线程。如果服务返回CompletableFuture<?>,则默认操作是将它作为发送到输出(或回复)通道的消息的有效负载。从版本 4.3 开始,您现在可以将async属性设置为true(在使用 Java 配置时使用setAsync(true))。如果服务在async属性设置为true时返回CompletableFuture<?>,则调用线程会立即释放,并且回复消息将在完成未来的线程(来自您的服务)上发送。这对于使用PollableChannel的长时间运行服务特别有利,因为轮询线程被释放以执行框架中的其他服务。

如果服务使用Exception完成未来,则会发生正常的错误处理。如果存在errorChannel消息头,则会向其发送ErrorMessage。否则,会向默认的errorChannel(如果可用)发送ErrorMessage

从版本 6.1 开始,如果AbstractMessageProducingHandler的输出通道配置为ReactiveStreamsSubscribableChannel,则默认情况下会打开异步模式。如果处理程序结果不是反应式类型或CompletableFuture<?>,则尽管输出通道类型,但会发生常规的回复生成过程。

有关更多信息,请参阅Reactive Streams 支持

服务激活器和方法返回类型

服务方法可以返回任何类型,该类型将成为回复消息的有效负载。在这种情况下,将创建一个新的Message<?>对象,并将来自请求消息的所有头复制到该对象。对于大多数 Spring Integration MessageHandler 实现,当交互基于 POJO 方法调用时,其工作方式相同。

也可以从方法返回完整的Message<?>对象。但是,请记住,与转换器不同,对于服务激活器,如果返回的消息中不存在请求消息头,则会通过复制请求消息头来修改此消息。因此,如果您的方法参数是Message<?>,并且您在服务方法中复制了一些现有头,但不是全部,它们将重新出现在回复消息中。服务激活器不负责从回复消息中删除头,并且为了遵循松散耦合原则,最好在集成流中添加HeaderFilter。或者,可以使用转换器而不是服务激活器,但在这种情况下,当返回完整的Message<?>时,方法将完全负责消息,包括复制请求消息头(如果需要)。您必须确保保留重要的框架头(例如replyChannelerrorChannel)(如果存在)。