385 lines
20 KiB
XML
385 lines
20 KiB
XML
<?xml version="1.0" encoding="UTF-8"?>
|
||
<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="message-publishing"
|
||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||
<title>Message Publishing</title>
|
||
<para>
|
||
The AOP Message Publishing feature allows you to construct and send a message as a by-product of a method invocation. For example, imagine you
|
||
have a component and every time the state of this component changes you would like to be notified via a Message. The easiest
|
||
way to send such notifications would be to send a message to a dedicated channel, but how would you connect the method invocation that
|
||
changes the state of the object to a message sending process, and how should the notification Message be structured?
|
||
The AOP Message Publishing feature handles these responsibilities with a configuration-driven approach.
|
||
</para>
|
||
<section id="message-publishing-config">
|
||
<title>Message Publishing Configuration</title>
|
||
<para>
|
||
Spring Integration provides two approaches: XML and Annotation-driven.
|
||
</para>
|
||
<section id="publisher-annotation">
|
||
<title>Annotation-driven approach via @Publisher annotation</title>
|
||
<para>
|
||
The annotation-driven approach allows you to annotate any method with the <interfacename>@Publisher</interfacename> annotation,
|
||
specifying a 'channel' attribute.
|
||
The Message will be constructed from the return value of the method invocation and sent to a channel specified by the 'channel' attribute.
|
||
To further manage message structure, you can also use a combination of both <interfacename>@Payload</interfacename>
|
||
and <interfacename>@Header</interfacename> annotations.
|
||
</para>
|
||
<para>
|
||
Internally this message publishing feature of Spring Integration uses both Spring AOP by defining
|
||
<classname>PublisherAnnotationAdvisor</classname> and
|
||
Spring 3.0's Expression Language (SpEL) support, giving you considerable flexibility and control over the structure of the
|
||
<emphasis>Message</emphasis> it will publish.
|
||
</para>
|
||
|
||
<para>
|
||
The <classname>PublisherAnnotationAdvisor</classname> defines and binds the following variables:
|
||
<itemizedlist>
|
||
<listitem>
|
||
<para><emphasis>#return</emphasis> - will bind to a return value allowing you to reference it or its
|
||
attributes (e.g., <emphasis>#return.foo</emphasis> where 'foo' is an attribute of the object bound to
|
||
<emphasis>#return</emphasis>)</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para><emphasis>#exception</emphasis> - will bind to an exception if one is thrown by the method invocation.</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para><emphasis>#args</emphasis> - will bind to method arguments, so individual arguments could be extracted by name
|
||
(e.g., <emphasis>#args.fname</emphasis> as in the above method)</para>
|
||
</listitem>
|
||
</itemizedlist>
|
||
</para>
|
||
|
||
<para>
|
||
Let's look at a couple of examples:
|
||
</para>
|
||
<para>
|
||
<programlisting language="java">@Publisher
|
||
public String defaultPayload(String fname, String lname) {
|
||
return fname + " " + lname;
|
||
}</programlisting>
|
||
</para>
|
||
<para>
|
||
In the above example the Message will be constructed with the following structure:
|
||
<itemizedlist>
|
||
<listitem>
|
||
<para>Message payload - will be the return type and value of the method. This is the default.</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>A newly constructed message will be sent to a default publisher channel configured with an annotation post processor (see the end of this section).</para>
|
||
</listitem>
|
||
</itemizedlist>
|
||
</para>
|
||
<para>
|
||
<programlisting language="java">@Publisher(channel="testChannel")
|
||
public String defaultPayload(String fname, @Header("last") String lname) {
|
||
return fname + " " + lname;
|
||
}</programlisting>
|
||
</para>
|
||
<para>
|
||
In this example everything is the same as above, except that we are not using a default publishing channel. Instead we are specifying
|
||
the publishing channel via the 'channel' attribute of the <interface>@Publisher</interface> annotation.
|
||
We are also adding a <interface>@Header</interface> annotation which results in the Message header named 'last' having the same value
|
||
as the 'lname' method parameter. That header will be added to the newly constructed Message.
|
||
</para>
|
||
|
||
<para>
|
||
<programlisting language="java">@Publisher(channel="testChannel")
|
||
@Payload
|
||
public String defaultPayloadButExplicitAnnotation(String fname, @Header String lname) {
|
||
return fname + " " + lname;
|
||
}</programlisting>
|
||
</para>
|
||
<para>
|
||
The above example is almost identical to the previous one. The only difference here is that we are using a <interface>@Payload</interface> annotation
|
||
on the method, thus explicitly specifying that the return value of the method should be used as the payload of the Message.
|
||
</para>
|
||
|
||
<para>
|
||
<programlisting language="java">@Publisher(channel="testChannel")
|
||
@Payload("#return + #args.lname")
|
||
public String setName(String fname, String lname, @Header("x") int num) {
|
||
return fname + " " + lname;
|
||
}</programlisting>
|
||
</para>
|
||
<para>
|
||
Here we are expanding on the previous configuration by using the Spring Expression Language in the
|
||
<interface>@Payload</interface> annotation to further instruct
|
||
the framework how the message should be constructed. In this particular case the message will be a concatenation of the return
|
||
value of the method invocation and the 'lname' input argument. The Message header named 'x' will have its value determined by
|
||
the 'num' input argument. That header will be added to the newly constructed Message.
|
||
</para>
|
||
|
||
<para>
|
||
<programlisting language="java">@Publisher(channel="testChannel")
|
||
public String argumentAsPayload(@Payload String fname, @Header String lname) {
|
||
return fname + " " + lname;
|
||
}</programlisting>
|
||
</para>
|
||
<para>
|
||
In the above example you see another usage of the <interface>@Payload</interface> annotation. Here we are annotating a method argument
|
||
which will become the payload of the newly constructed message.
|
||
</para>
|
||
|
||
<para>
|
||
As with most other annotation-driven features in Spring, you will need to register a post-processor
|
||
(<classname>PublisherAnnotationBeanPostProcessor</classname>).
|
||
<programlisting language="xml"><bean class="org.springframework.integration.aop.PublisherAnnotationBeanPostProcessor"/></programlisting>
|
||
You can instead use namespace support for a more concise configuration:
|
||
|
||
<programlisting language="xml"><si:annotation-config default-publisher-channel="defaultChannel"/></programlisting>
|
||
</para>
|
||
|
||
<para>
|
||
Similar to other Spring annotations (@Component, @Scheduled, etc.), <classname>@Publisher</classname> can also be used as a meta-annotation.
|
||
That means you can define your own annotations
|
||
that will be treated in the same way as the <classname>@Publisher</classname> itself.
|
||
<programlisting language="java"><![CDATA[@Target({ElementType.METHOD, ElementType.TYPE})
|
||
@Retention(RetentionPolicy.RUNTIME)
|
||
@Publisher(channel="auditChannel")
|
||
public @interface Audit {
|
||
}]]></programlisting>
|
||
Here we defined the <classname>@Audit</classname> annotation which itself is annotated with <classname>@Publisher</classname>.
|
||
Also note that you can define a <code>channel</code>
|
||
attribute on the meta-annotation thus encapsulating the behavior of where messages will be sent inside of this annotation.
|
||
|
||
Now you can annotate any method:
|
||
<programlisting language="java"><![CDATA[@Audit
|
||
public String test() {
|
||
return "foo";
|
||
}]]></programlisting>
|
||
|
||
In the above example every invocation of the <code>test()</code> method will result in a Message with a payload created from its return value.
|
||
Each Message will be sent to the channel named <emphasis>auditChannel</emphasis>. One of the benefits of this technique is that you can
|
||
avoid the duplication of the same channel name across multiple annotations. You also can provide a level of indirection between your own,
|
||
potentially domain-specific annotations and those provided by the framework.
|
||
</para>
|
||
|
||
<para>
|
||
You can also annotate the class which would mean that the properties of this annotation will be applied on every public method of that class.
|
||
|
||
<programlisting language="java"><![CDATA[@Audit
|
||
static class BankingOperationsImpl implements BankingOperations {
|
||
|
||
public String debit(String amount) {
|
||
. . .
|
||
}
|
||
|
||
public String credit(String amount) {
|
||
. . .
|
||
}
|
||
}]]></programlisting>
|
||
|
||
</para>
|
||
</section>
|
||
|
||
<section id="aop-based-interceptor">
|
||
<title>XML-based approach via the <publishing-interceptor> element</title>
|
||
<para>
|
||
The XML-based approach allows you to configure the same AOP-based Message Publishing functionality with
|
||
simple namespace-based configuration of a <classname>MessagePublishingInterceptor</classname>.
|
||
It certainly has some benefits over the annotation-driven approach since it
|
||
allows you to use AOP pointcut expressions, thus possibly intercepting multiple methods at once or
|
||
intercepting and publishing methods to which you don't have the source code.
|
||
</para>
|
||
<para>
|
||
To configure Message Publishing via XML, you only need to do the following two things:
|
||
<itemizedlist>
|
||
<listitem>
|
||
<para>Provide configuration for <classname>MessagePublishingInterceptor</classname>
|
||
via the <code><publishing-interceptor></code> XML element.</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>Provide AOP configuration to apply the <classname>MessagePublishingInterceptor</classname> to managed objects.</para>
|
||
</listitem>
|
||
</itemizedlist>
|
||
</para>
|
||
<para>
|
||
<programlisting language="xml"><![CDATA[<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="foo" value="bar"/>
|
||
</method>
|
||
<method pattern="repl*" payload="'Echoing: ' + #return" channel="echoChannel">
|
||
<header name="foo" expression="'bar'.toUpperCase()"/>
|
||
</method>
|
||
<method pattern="echoDef*" payload="#return"/>
|
||
</publishing-interceptor>]]></programlisting>
|
||
</para>
|
||
<para>
|
||
As you can see the <code><publishing-interceptor></code> configuration looks rather similar to the Annotation-based approach,
|
||
and it also utilizes the power of the Spring 3.0 Expression Language.
|
||
</para>
|
||
<para>
|
||
In the above example the execution of the <code>echo</code> method of a <code>testBean</code> will
|
||
render a <emphasis>Message</emphasis> with the following structure:
|
||
<itemizedlist>
|
||
<listitem>
|
||
<para>The Message payload will be of type String with the content "Echoing: [value]" where <code>value</code> is the value
|
||
returned by an executed method.</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>The Message will have a header with the name "foo" and value "bar".</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>The Message will be sent to <code>echoChannel</code>.</para>
|
||
</listitem>
|
||
</itemizedlist>
|
||
</para>
|
||
|
||
<para>
|
||
The second method is very similar to the first. Here every method that begins with 'repl' will render a Message with the following structure:
|
||
<itemizedlist>
|
||
<listitem>
|
||
<para>The Message payload will be the same as in the above sample</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>The Message will have a header named "foo" whose value is the result of the SpEL expression <code>'bar'.toUpperCase()</code> .</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>The Message will be sent to <code>echoChannel</code>.</para>
|
||
</listitem>
|
||
</itemizedlist>
|
||
</para>
|
||
|
||
<para>
|
||
The second method, mapping the execution of any method that begins with <code>echoDef</code> of <code>testBean</code>, will produce a
|
||
Message with the following structure.
|
||
<itemizedlist>
|
||
<listitem>
|
||
<para>The Message payload will be the value returned by an executed method.</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>Since the <code>channel</code> attribute is not provided explicitly, the Message will be sent to the
|
||
<code>defaultChannel</code> defined by the <emphasis>publisher</emphasis>.</para>
|
||
</listitem>
|
||
</itemizedlist>
|
||
</para>
|
||
|
||
<para>
|
||
For simple mapping rules you can rely on the <emphasis>publisher</emphasis> defaults. For example:
|
||
<programlisting language="xml">
|
||
<publishing-interceptor id="anotherInterceptor"/>
|
||
</programlisting>
|
||
This will map the return value of every method that matches the pointcut expression to a payload and will be sent to a <emphasis>default-channel</emphasis>.
|
||
If the <emphasis>defaultChannel</emphasis>is not specified (as above) the messages will be sent to the global <emphasis>nullChannel</emphasis>.
|
||
</para>
|
||
<para>
|
||
<emphasis>Async Publishing</emphasis>
|
||
</para>
|
||
<para>
|
||
One important thing to understand is that publishing occurs in the same thread as your component's execution. So by default in is synchronous.
|
||
This means that the entire message flow would have to wait until the publisher's flow completes.
|
||
However, quite often you want the complete opposite and that is to use this Message publishing feature to initiate asynchronous sub-flows.
|
||
For example, you might host a service (HTTP, WS etc.) which receives a remote request.You may want to send this request internally into a
|
||
process that might take a while. However you may also want to reply to the user right away. So, instead of sending inbound
|
||
requests for processing via the output channel (the conventional way), you can simply use 'output-channel' or a 'replyChannel' header
|
||
to send a simple acknowledgment-like reply back to the caller while using the Message publisher feature to initiate a complex flow.
|
||
</para>
|
||
<para>
|
||
EXAMPLE:
|
||
Here is the simple service that receives a complex payload, which needs to be sent further for processing, but it
|
||
also needs to reply to the caller with a simple acknowledgment.
|
||
<programlisting language="java"><![CDATA[public String echo(Object complexPayload) {
|
||
return "ACK";
|
||
}]]></programlisting>
|
||
So instead of hooking up the complex flow to the output channel we use the Message publishing feature instead. We configure it to create a
|
||
new Message using the input argument of the service method (above) and send that to the 'localProcessChannel'. And to make sure this sub-flow
|
||
is asynchronous all we need to do is send it to any type of asynchronous channel (ExecutorChannel in this example).
|
||
<programlisting language="xml"><![CDATA[<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"/>]]></programlisting>
|
||
</para>
|
||
<para>
|
||
Another way of handling this type of scenario is with a wire-tap.
|
||
</para>
|
||
</section>
|
||
|
||
<section id="scheduled-producer">
|
||
<title>Producing and publishing messages based on a scheduled trigger</title>
|
||
<para>
|
||
In the above sections we looked at the Message publishing feature of Spring Integration which constructs and publishes messages as by-products of Method invocations.
|
||
However in those cases, you are still responsible for invoking the method.
|
||
In Spring Integration 2.0 we've added another related useful feature: support for scheduled Message producers/publishers via the new "expression" attribute
|
||
on the 'inbound-channel-adapter' element. Scheduling could be based on several triggers, any one of which may be configured on the 'poller' sub-element.
|
||
Currently we support <code>cron</code>, <code>fixed-rate</code>, <code>fixed-delay</code> as well as any custom trigger implemented by you and
|
||
referenced by the 'trigger' attribute value.
|
||
</para>
|
||
<para>
|
||
As mentioned above, support for scheduled producers/publishers is provided via the <emphasis><inbound-channel-adapter></emphasis> xml element.
|
||
Let's look at couple of examples:
|
||
</para>
|
||
<para>
|
||
|
||
<programlisting language="xml"><![CDATA[<inbound-channel-adapter id="fixedDelayProducer"
|
||
expression="'fixedDelayTest'"
|
||
channel="fixedDelayChannel">
|
||
<poller fixed-delay="1000"/>
|
||
</inbound-channel-adapter>]]></programlisting>
|
||
|
||
In the above example an inbound Channel Adapter will be created which will construct a Message with its payload being the result of the expression
|
||
defined in the <code>expression</code> attribute. Such messages will be created and sent every time the delay specified by the <code>fixed-delay</code> attribute occurs.
|
||
|
||
<programlisting language="xml"><![CDATA[<inbound-channel-adapter id="fixedRateProducer"
|
||
expression="'fixedRateTest'"
|
||
channel="fixedRateChannel">
|
||
<poller fixed-rate="1000"/>
|
||
</inbound-channel-adapter>]]></programlisting>
|
||
|
||
This example is very similar to the previous one, except that we are using the <code>fixed-rate</code> attribute which will allow us to send messages at a fixed rate (measuring from the start time of each task).
|
||
|
||
<programlisting language="xml"><![CDATA[<inbound-channel-adapter id="cronProducer"
|
||
expression="'cronTest'"
|
||
channel="cronChannel">
|
||
<poller cron="7 6 5 4 3 ?"/>
|
||
</inbound-channel-adapter>]]></programlisting>
|
||
|
||
This example demonstrates how you can apply a Cron trigger with a value specified in the <code>cron</code> attribute.
|
||
|
||
<programlisting language="xml"><![CDATA[<inbound-channel-adapter id="headerExpressionsProducer"
|
||
expression="'headerExpressionsTest'"
|
||
channel="headerExpressionsChannel"
|
||
auto-startup="false">
|
||
<poller fixed-delay="5000"/>
|
||
<header name="foo" expression="6 * 7"/>
|
||
<header name="bar" value="x"/>
|
||
</inbound-channel-adapter>]]></programlisting>
|
||
|
||
Here you can see that in a way very similar to the Message publishing feature we are enriching a newly constructed Message with
|
||
extra Message headers which can take scalar values or the results of evaluating Spring expressions.
|
||
</para>
|
||
|
||
<para>
|
||
If you need to implement your own custom trigger you can use the <code>trigger</code> attribute to provide a reference to any spring configured
|
||
bean which implements the <classname>org.springframework.scheduling.Trigger</classname> interface.
|
||
|
||
<programlisting language="xml"><![CDATA[<inbound-channel-adapter id="triggerRefProducer"
|
||
expression="'triggerRefTest'" channel="triggerRefChannel">
|
||
<poller trigger="customTrigger"/>
|
||
</inbound-channel-adapter>
|
||
|
||
<beans:bean id="customTrigger" class="org.springframework.scheduling.support.PeriodicTrigger">
|
||
<beans:constructor-arg value="9999"/>
|
||
</beans:bean>]]></programlisting>
|
||
|
||
</para>
|
||
</section>
|
||
</section>
|
||
</chapter>
|