(面向方面的编程)AOP 消息发布功能允许您构造消息并将其作为方法调用的副产品发送。 例如,假设您有一个组件,并且每次该组件的状态更改时,您都希望收到消息通知。 发送此类通知的最简单方法是将消息发送到专用通道,但是如何将更改对象状态的方法调用连接到消息发送进程,以及通知消息的结构应该如何? AOP 消息发布功能通过配置驱动的方法处理这些职责。
消息发布配置
Spring Integration 提供了两种方法:XML 配置和注释驱动 (Java) 配置。
使用注释的注释驱动配置@Publisher
注释驱动的方法允许您使用注释对任何方法进行注释,以指定“通道”属性。
从 V5.1 开始,要打开此功能,必须在某个类上使用注释。
有关详细信息,请参阅配置和@EnableIntegration
。
消息是根据方法调用的返回值构造的,并发送到“channel”属性指定的通道。
若要进一步管理消息结构,还可以同时使用两者和注释的组合。@Publisher
@EnablePublisher
@Configuration
@Payload
@Header
在内部,Spring Integration 的此消息发布功能通过定义使用 Spring AOP 和 Spring 表达式语言 (SpEL),为您提供了相当大的灵活性和对它发布的结构的控制。PublisherAnnotationAdvisor
Message
定义并绑定以下变量:PublisherAnnotationAdvisor
-
#return
:绑定到返回值,允许您引用它或其属性(例如,其中 'something' 是绑定到的对象的属性#return.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;
}
我们没有使用默认的发布渠道,而是通过设置注释的“channel”属性来指定发布渠道。
我们还添加了一个注释,这会导致名为“last”的消息标头与“lname”方法参数具有相同的值。
该标头将添加到新构造的消息中。@Publisher
@Header
以下示例与前面的示例几乎相同:
@Publisher(channel="testChannel")
@Payload
public String defaultPayloadButExplicitAnnotation(String fname, @Header String lname) {
return fname + " " + lname;
}
唯一的区别是,我们在方法上使用注释来显式指定方法的返回值应用作消息的有效负载。@Payload
以下示例通过在注释中使用 Spring 表达式语言来扩展前面的配置,以进一步指示框架如何构造消息:@Payload
@Publisher(channel="testChannel")
@Payload("#return + #args.lname")
public String setName(String fname, String lname, @Header("x") int num) {
return fname + " " + lname;
}
在前面的示例中,消息是方法调用的返回值和“lname”输入参数的串联。 名为“x”的 Message 标头的值由“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 开始,组件和注释都具有用于调整配置的 and 属性。<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) {
. . .
}
}
带有元素的基于 XML 的方法<publishing-interceptor>
通过基于 XML 的方法,您可以配置与基于命名空间的 .
与注释驱动的方法相比,它当然有一些好处,因为它允许您使用 AOP 切入表达式,因此可以同时拦截多个方法,或者拦截和发布您没有源代码的方法。MessagePublishingInterceptor
若要使用 XML 配置消息发布,只需执行以下两项操作:
-
使用 XML 元素提供配置。
MessagePublishingInterceptor
<publishing-interceptor>
-
提供 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>
该配置看起来与基于注释的方法非常相似,并且它还使用了 Spring 表达式语言的强大功能。<publishing-interceptor>
在前面的示例中,a 方法的执行呈现具有以下结构的 a:echo
testBean
Message
-
有效负载的类型具有以下内容: ,其中 是执行方法返回的值。
Message
String
Echoing: [value]
value
-
具有名称为 且值为 的标头。
Message
things
something
-
将发送到 。
Message
echoChannel
第二种方法与第一种方法非常相似。
在这里,每个以 'repl' 开头的方法都会呈现具有以下结构的方法:Message
-
有效负载与前面的示例相同。
Message
-
具有一个名为其值的标头,其值是 SpEL 表达式的结果。
Message
things
'something'.toUpperCase()
-
将发送到 。
Message
echoChannel
第二种方法映射以 开头的任何方法的执行,生成具有以下结构的方法:echoDef
Message
-
有效负载是执行的方法返回的值。
Message
-
由于未提供该属性,因此将 发送到 定义的 .
channel
Message
defaultChannel
publisher
对于简单的映射规则,您可以依赖默认值,如以下示例所示:publisher
<publishing-interceptor id="anotherInterceptor"/>
前面的示例将与 pointcut 表达式匹配的每个方法的返回值映射到有效负载,并发送到 .
如果未指定 (如前面的示例所示),则消息将发送到全局(相当于 )。default-channel
defaultChannel
nullChannel
/dev/null
异步发布
发布与组件执行在同一线程中进行。 因此,默认情况下,它是同步的。 这意味着整个消息流必须等到发布者的流完成。 但是,开发人员通常希望完全相反:使用此消息发布功能来启动异步流。 例如,您可能托管接收远程请求的服务(HTTP、WS 等)。 您可能希望在内部将此请求发送到可能需要一段时间的进程中。 但是,您可能还希望立即回复用户。 因此,您可以使用“output-channel”或“replyChannel”标头向调用方发送简单的类似确认的回复,同时使用 message-publisher 功能启动复杂的流,而不是将入站请求发送到输出通道进行处理(传统方式)。
以下示例中的服务接收复杂的有效负载(需要进一步发送以进行处理),但它也需要通过简单的确认来回复调用方:
public String echo(Object complexPayload) {
return "ACK";
}
因此,我们没有将复杂的流连接到输出通道,而是使用消息发布功能。
我们通过使用 service 方法的 input 参数(如前面的示例所示)将其配置为创建新消息,并将其发送到“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"/>
处理此类情况的另一种方法是使用窃听器。 请参阅窃听器。
基于计划触发器生成和发布消息
在前面的章节中,我们研究了消息发布功能,该功能将消息构造和发布为方法调用的副产品。
但是,在这些情况下,您仍需负责调用该方法。
Spring Integration 2.0 添加了对计划消息制作者和发布者的支持,并在“inbound-channel-adapter”元素上添加了新属性。
您可以基于多个触发器进行计划,其中任何一个都可以在“poller”元素上配置。
目前,我们支持 、 以及由您实现并由“trigger”属性值引用的任何自定义触发器。expression
cron
fixed-rate
fixed-delay
如前所述,通过 XML 元素提供对计划生产者和发布者的支持。
请看以下示例:<inbound-channel-adapter>
<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 表达式的结果。
如果需要实现自己的自定义触发器,可以使用该属性提供对实现接口的任何 Spring 配置的 Bean 的引用。
以下示例演示如何执行此操作:trigger
org.springframework.scheduling.Trigger
<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>