消息发布
面向切面编程 (AOP) 消息发布功能让您可以在方法调用完成后构造和发送消息。例如,假设您有一个组件,并且每次此组件的状态更改时,您都希望通过消息收到通知。发送此类通知的最简单方法是将消息发送到专用通道,但是如何将更改对象状态的方法调用连接到消息发送过程,以及通知消息应该如何结构化?AOP 消息发布功能通过配置驱动的方法处理这些职责。
消息发布配置
Spring Integration 提供两种方法:XML 配置和注解驱动 (Java) 配置。
使用 @Publisher 注解的注解驱动配置
注解驱动的方法允许您使用 @Publisher 注解来标注任何方法以指定“通道”属性。从 5.1 版本开始,要启用此功能,您必须在某些 @Configuration 类上使用 @EnablePublisher 注解。有关更多信息,请参阅配置和 @EnableIntegration。消息由方法调用的返回值构造并发送到“通道”属性指定的通道。要进一步管理消息结构,您还可以结合使用 @Payload 和 @Header 注解。
在内部,Spring Integration 的此消息发布功能通过定义 PublisherAnnotationAdvisor 和 Spring Expression Language (SpEL) 使用 Spring AOP,为您提供了极大的灵活性和对发布消息结构的控制。
PublisherAnnotationAdvisor 定义并绑定以下变量
-
#return:绑定到返回值,允许您引用它或它的属性(例如,#return.something,其中“something”是绑定到#return的对象的属性) -
#exception:如果方法调用抛出异常,则绑定到异常 -
#args:绑定到方法参数,以便您可以通过名称提取单个参数(例如,#args.fname)
考虑以下示例
@Publisher
public String defaultPayload(String fname, String lname) {
return fname + " " + lname;
}
在前面的示例中,消息以以下结构构造
-
消息载荷是方法的返回类型和值。这是默认值。
-
新构造的消息被发送到默认的发布者通道,该通道使用注解后处理器(本节后面会介绍)进行配置。
以下示例与前面的示例相同,只是它不使用默认的发布通道
@Publisher(channel="testChannel")
public String defaultPayload(String fname, @Header("last") String lname) {
return fname + " " + lname;
}
我们不使用默认的发布通道,而是通过设置 @Publisher 注解的“通道”属性来指定发布通道。我们还添加了一个 @Header 注解,这导致名为“last”的消息头具有与“lname”方法参数相同的值。该头被添加到新构造的消息中。
以下示例与前面的示例几乎相同
@Publisher(channel="testChannel")
@Payload
public String defaultPayloadButExplicitAnnotation(String fname, @Header String lname) {
return fname + " " + lname;
}
唯一的区别是我们在方法上使用 @Payload 注解来明确指定方法的返回值应作为消息的载荷。
以下示例通过在 @Payload 注解中使用 Spring Expression Language 进一步扩展了之前的配置,以指示框架如何构造消息
@Publisher(channel="testChannel")
@Payload("#return + #args.lname")
public String setName(String fname, String lname, @Header("x") int num) {
return fname + " " + lname;
}
在前面的示例中,消息是方法调用的返回值和“lname”输入参数的连接。名为“x”的消息头的值由“num”输入参数确定。该头被添加到新构造的消息中。
@Publisher(channel="testChannel")
public String argumentAsPayload(@Payload String fname, @Header String lname) {
return fname + " " + lname;
}
在前面的示例中,您看到了 @Payload 注解的另一种用法。在这里,我们标注了一个方法参数,该参数成为新构造消息的载荷。
与 Spring 中的大多数其他注解驱动功能一样,您需要注册一个后处理器 (PublisherAnnotationBeanPostProcessor)。以下示例展示了如何执行此操作
<bean class="org.springframework.integration.aop.PublisherAnnotationBeanPostProcessor"/>
对于更简洁的配置,您可以转而使用命名空间支持,如下面的示例所示
<int:annotation-config>
<int:enable-publisher default-publisher-channel="defaultChannel"/>
</int:annotation-config>
对于 Java 配置,您必须使用 @EnablePublisher 注解,如下面的示例所示
@Configuration
@EnableIntegration
@EnablePublisher("defaultChannel")
public class IntegrationConfiguration {
...
}
从 5.1.3 版本开始,<int:enable-publisher> 组件以及 @EnablePublisher 注解具有 proxy-target-class 和 order 属性,用于调整 ProxyFactory 配置。
与其他 Spring 注解(@Component、@Scheduled 等)类似,您还可以将 @Publisher 用作元注解。这意味着您可以定义自己的注解,这些注解与 @Publisher 本身的处理方式相同。以下示例展示了如何执行此操作
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Publisher(channel="auditChannel")
public @interface Audit {
...
}
在前面的示例中,我们定义了 @Audit 注解,它本身被 @Publisher 注解。另请注意,您可以在元注解上定义一个 channel 属性,以封装此注解内部发送消息的位置。现在,您可以使用 @Audit 注解标注任何方法,如下面的示例所示
@Audit
public String test() {
return "Hello";
}
在前面的示例中,每次调用 test() 方法都会生成一个消息,其载荷由其返回值创建。每条消息都发送到名为 auditChannel 的通道。此技术的好处之一是您可以避免在多个注解中重复使用相同的通道名称。您还可以为您自己的(可能是特定领域的)注解和框架提供的注解之间提供一层间接。
您还可以标注类,这允许您将此注解的属性应用于该类的每个公共方法,如下面的示例所示
@Audit
static class BankingOperationsImpl implements BankingOperations {
public String debit(String amount) {
. . .
}
public String credit(String amount) {
. . .
}
}
使用 <publishing-interceptor> 元素的基于 XML 的方法
基于 XML 的方法允许您将相同的基于 AOP 的消息发布功能配置为 MessagePublishingInterceptor 的基于命名空间的配置。它确实比注解驱动的方法有一些好处,因为它允许您使用 AOP 切入点表达式,从而可能一次拦截多个方法或拦截并发布您没有源代码的方法。
要使用 XML 配置消息发布,您只需执行以下两件事
-
使用
<publishing-interceptor>XML 元素为MessagePublishingInterceptor提供配置。 -
提供 AOP 配置以将
MessagePublishingInterceptor应用于托管对象。
以下示例展示了如何配置 publishing-interceptor 元素
<aop:config>
<aop:advisor advice-ref="interceptor" pointcut="bean(testBean)" />
</aop:config>
<publishing-interceptor id="interceptor" default-channel="defaultChannel">
<method pattern="echo" payload="'Echoing: ' + #return" channel="echoChannel">
<header name="things" value="something"/>
</method>
<method pattern="repl*" payload="'Echoing: ' + #return" channel="echoChannel">
<header name="things" expression="'something'.toUpperCase()"/>
</method>
<method pattern="echoDef*" payload="#return"/>
</publishing-interceptor>
<publishing-interceptor> 配置看起来与基于注解的方法非常相似,它也使用了 Spring Expression Language 的强大功能。
在前面的示例中,testBean 的 echo 方法的执行会呈现一个具有以下结构的消息
-
Message载荷类型为String,内容如下:Echoing: [value],其中value是执行方法返回的值。 -
Message有一个名为things的头,其值为something。 -
Message被发送到echoChannel。
第二种方法与第一种非常相似。在这里,每个以“repl”开头的方法都会呈现一个具有以下结构的消息
-
Message载荷与前面的示例相同。 -
Message有一个名为things的头,其值是 SpEL 表达式'something'.toUpperCase()的结果。 -
Message被发送到echoChannel。
第二种方法,映射任何以 echoDef 开头的方法的执行,会生成一个具有以下结构的消息
-
Message载荷是执行方法返回的值。 -
由于未提供
channel属性,因此Message被发送到publisher定义的defaultChannel。
对于简单的映射规则,您可以依赖 publisher 默认值,如下面的示例所示
<publishing-interceptor id="anotherInterceptor"/>
前面的示例将与切入点表达式匹配的每个方法的返回值映射到载荷,并发送到 default-channel。如果您未指定 defaultChannel(如前面的示例所示),则消息将发送到全局 nullChannel(相当于 /dev/null)。
异步发布
发布与组件的执行发生在同一线程中。因此,默认情况下,它是同步的。这意味着整个消息流必须等待发布者的流完成。然而,开发人员通常希望完全相反:使用此消息发布功能来启动异步流。例如,您可能托管一个服务(HTTP、WS 等),该服务接收远程请求。您可能希望将此请求内部发送到可能需要一段时间才能处理的进程。但是,您也可能希望立即回复用户。因此,与其将入站请求发送到输出通道进行处理(传统方式),不如使用“output-channel”或“replyChannel”头向调用者发送一个简单的确认式回复,同时使用消息发布功能启动复杂的流。
以下示例中的服务接收复杂的载荷(需要进一步发送以进行处理),但它还需要向调用者回复一个简单的确认
public String echo(Object complexPayload) {
return "ACK";
}
因此,我们不将复杂的流连接到输出通道,而是使用消息发布功能。我们将其配置为使用服务方法的输入参数(如前面的示例所示)创建一条新消息,并将其发送到“localProcessChannel”。为了确保此流是异步的,我们只需将其发送到任何类型的异步通道(下一个示例中的 ExecutorChannel)。以下示例展示了如何配置异步 publishing-interceptor
<int:service-activator input-channel="inputChannel" output-channel="outputChannel" ref="sampleservice"/>
<bean id="sampleService" class="test.SampleService"/>
<aop:config>
<aop:advisor advice-ref="interceptor" pointcut="bean(sampleService)" />
</aop:config>
<int:publishing-interceptor id="interceptor" >
<int:method pattern="echo" payload="#args[0]" channel="localProcessChannel">
<int:header name="sample_header" expression="'some sample value'"/>
</int:method>
</int:publishing-interceptor>
<int:channel id="localProcessChannel">
<int:dispatcher task-executor="executor"/>
</int:channel>
<task:executor id="executor" pool-size="5"/>
处理此类场景的另一种方法是使用 wire-tap。请参阅Wire Tap。
基于计划触发器生成和发布消息
在前面的章节中,我们研究了消息发布功能,该功能将消息构造并发布为方法调用的副产品。但是,在这些情况下,您仍然负责调用方法。Spring Integration 2.0 添加了对计划消息生产者和发布者的支持,在“inbound-channel-adapter”元素上新增了 expression 属性。您可以基于几个触发器进行计划,其中任何一个都可以在“poller”元素上进行配置。目前,我们支持 cron、fixed-rate、fixed-delay 以及您实现并通过“trigger”属性值引用的任何自定义触发器。
如前所述,对计划生产者和发布者的支持是通过 <inbound-channel-adapter> XML 元素提供的。请考虑以下示例
<int:inbound-channel-adapter id="fixedDelayProducer"
expression="'fixedDelayTest'"
channel="fixedDelayChannel">
<int:poller fixed-delay="1000"/>
</int:inbound-channel-adapter>
前面的示例创建了一个入站通道适配器,该适配器构造一个 Message,其载荷是 expression 属性中定义的表达式的结果。每当发生 fixed-delay 属性指定的延迟时,就会创建并发送此类消息。
以下示例与前面的示例类似,只是它使用了 fixed-rate 属性
<int:inbound-channel-adapter id="fixedRateProducer"
expression="'fixedRateTest'"
channel="fixedRateChannel">
<int:poller fixed-rate="1000"/>
</int:inbound-channel-adapter>
fixed-rate 属性允许您以固定速率发送消息(从每个任务的开始时间测量)。
以下示例展示了如何应用 Cron 触发器,其值在 cron 属性中指定
<int:inbound-channel-adapter id="cronProducer"
expression="'cronTest'"
channel="cronChannel">
<int:poller cron="7 6 5 4 3 ?"/>
</int:inbound-channel-adapter>
以下示例展示了如何向消息中插入额外的头
<int:inbound-channel-adapter id="headerExpressionsProducer"
expression="'headerExpressionsTest'"
channel="headerExpressionsChannel"
auto-startup="false">
<int:poller fixed-delay="5000"/>
<int:header name="foo" expression="6 * 7"/>
<int:header name="bar" value="x"/>
</int:inbound-channel-adapter>
附加消息头可以采用标量值或评估 Spring 表达式的结果。
如果您需要实现自己的自定义触发器,可以使用 trigger 属性提供对实现 org.springframework.scheduling.Trigger 接口的任何 Spring 配置 bean 的引用。以下示例展示了如何执行此操作
<int:inbound-channel-adapter id="triggerRefProducer"
expression="'triggerRefTest'" channel="triggerRefChannel">
<int:poller trigger="customTrigger"/>
</int:inbound-channel-adapter>
<beans:bean id="customTrigger" class="o.s.scheduling.support.PeriodicTrigger">
<beans:constructor-arg value="9999"/>
</beans:bean>