Allow configuration of request handler advice chain using - ServiceActivator - Filter - Splitter - Transformer annotations. Also, with splitter, allow setting discardWithinAdvice (See INT-2938). INT-2994 Polishing: PR Comments - Change adviceChain attribute to an array - Use a boolean for the discardWithinAdvice attribute INT-2994 Handler Advice Doc Polishing Add a paragraph about Advice Order.
520 lines
28 KiB
XML
520 lines
28 KiB
XML
<?xml version="1.0" encoding="UTF-8"?>
|
|
<section version="5.0" xml:id="message-handler-advice-chain" xmlns="http://docbook.org/ns/docbook"
|
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
xmlns:ns5="http://www.w3.org/1999/xhtml"
|
|
xmlns:ns4="http://www.w3.org/1998/Math/MathML"
|
|
xmlns:ns3="http://www.w3.org/2000/svg"
|
|
xmlns:ns="http://docbook.org/ns/docbook">
|
|
<title>Adding Behavior to Endpoints</title>
|
|
|
|
<para>
|
|
Prior to Spring Integration 2.2, you could add behavior to an entire Integration flow by adding
|
|
an AOP Advice to a poller's <advice-chain /> element. However, let's say
|
|
you want to retry, say, just a ReST Web Service call, and not any downstream endpoints.
|
|
</para>
|
|
<para>
|
|
For example, consider the following flow:
|
|
</para>
|
|
<para>
|
|
<emphasis>inbound-adapter->poller->http-gateway1->http-gateway2->jdbc-outbound-adapter</emphasis>
|
|
</para>
|
|
<para>
|
|
If you configure some retry-logic into an advice chain on the poller, and, the call to
|
|
<emphasis>http-gateway2</emphasis> failed because of a network glitch, the retry would cause
|
|
both <emphasis>http-gateway1</emphasis> and <emphasis>http-gateway2</emphasis> to be called
|
|
a second time. Similarly, after a transient failure in the
|
|
<emphasis>jdbc-outbound-adapter</emphasis>, both http-gateways
|
|
would be called a second time before again calling the <emphasis>jdbc-outbound-adapter</emphasis>.
|
|
</para>
|
|
<para>
|
|
Spring Integration 2.2 adds the ability to add behavior to individual endpoints. This is achieved
|
|
by the addition of the <request-handler-advice-chain /> element to many endpoints. For example:
|
|
</para>
|
|
<para><programlisting language="xml"><![CDATA[<int-http:outbound-gateway id="withAdvice"
|
|
url-expression="'http://localhost/test1'"
|
|
request-channel="requests"
|
|
reply-channel="nextChannel">
|
|
<int:request-handler-advice-chain>
|
|
<ref bean="myRetryAdvice" />
|
|
</request-handler-advice-chain>
|
|
</int-http:outbound-gateway>]]></programlisting></para>
|
|
<para>
|
|
In this case, <emphasis>myRetryAdvice</emphasis> will only be applied locally to this gateway and
|
|
will not apply to further actions taken downstream after the reply is sent to the
|
|
<emphasis>nextChannel</emphasis>. The scope of the advice is limited to the endpoint itself.
|
|
</para>
|
|
<important>
|
|
<para>
|
|
At this time, you cannot advise an entire <chain/> of endpoints. The schema does not allow
|
|
a <request-handler-advice-chain/> as a child element of the chain itself.
|
|
</para>
|
|
<para>
|
|
However, a <request-handler-advice-chain/> can be added to individual reply-producing endpoints
|
|
<emphasis>within</emphasis> a <chain/> element.
|
|
An exception is that, in a chain that produces no reply, because the last element in the chain is an
|
|
<emphasis>outbound-channel-adapter</emphasis>, that <emphasis>last</emphasis> element cannot be advised. If you
|
|
need to advise such an element, it must be moved outside of the chain (with the
|
|
<emphasis>output-channel</emphasis> of the chain being the <emphasis>input-channel</emphasis> of
|
|
the adapter. The adapter can then be advised as normal. For chains that produce a reply, every child
|
|
element can be advised.
|
|
</para>
|
|
</important>
|
|
<section id="advice-classes">
|
|
<title>Provided Advice Classes</title>
|
|
<para>
|
|
In addition to providing the general mechanism to apply AOP Advice classes in this way, three
|
|
standard Advices are provided:
|
|
</para>
|
|
<itemizedlist>
|
|
<listitem>RequestHandlerRetryAdvice</listitem>
|
|
<listitem>RequestHandlerCircuitBreakerAdvice</listitem>
|
|
<listitem>ExpressionEvaluatingRequestHandlerAdvice</listitem>
|
|
</itemizedlist>
|
|
<para>
|
|
These are each described in detail in the following sections.
|
|
</para>
|
|
<section id="retry-advice">
|
|
<title>Retry Advice</title>
|
|
<para>
|
|
The retry advice (<classname>o.s.i.handler.advice.RequestHandlerRetryAdvice</classname>)
|
|
leverages the rich retry mechanisms provided by the
|
|
<ulink url="https://github.com/SpringSource/spring-retry">spring-retry</ulink> project. The core component
|
|
of <emphasis>spring-retry</emphasis> is the <classname>RetryTemplate</classname>, which allows configuration
|
|
of sophisticated retry scenarios, including <classname>RetryPolicy</classname> and <classname>BackoffPolicy</classname>
|
|
strategies, with a number of implementations,
|
|
as well as a <classname>RecoveryCallback</classname> strategy to determine the action to take when retries
|
|
are exhausted.
|
|
</para>
|
|
<para><emphasis>Stateless Retry</emphasis></para>
|
|
<para>
|
|
Stateless retry is the case where the retry activity is handled entirely within the advice, where the thread
|
|
pauses (if so configured) and retries the action.
|
|
</para>
|
|
<para><emphasis>Stateful Retry</emphasis></para>
|
|
<para>
|
|
Stateful retry is the case where the retry state is managed within the advice, but where an exception is thrown
|
|
and the caller resubmits the request. An example for stateful retry is when we want the message originator
|
|
(e.g. JMS) to be responsible for resubmitting, rather than performing it on the current thread. Stateful retry
|
|
needs some mechanism to detect a retried submission.
|
|
</para>
|
|
<para><emphasis>Further Information</emphasis></para>
|
|
<para>
|
|
For more information on <emphasis>spring-retry</emphasis>, refer to the project's javadocs, as well as the
|
|
reference documentation for <ulink url="http://static.springsource.org/spring-batch/reference/html/retry.html">
|
|
Spring Batch</ulink>, where <emphasis>spring-retry</emphasis> originated.
|
|
</para>
|
|
<caution>
|
|
The default back off behavior is no back off - retries are attempted immediately.
|
|
Using a back off policy that causes threads to pause between attempts may cause performance issues, including
|
|
excessive memory use and thread starvation. In high volume environments, back off policies should be used
|
|
with caution.
|
|
</caution>
|
|
<section id="retry-config">
|
|
<title>Configuring the Retry Advice</title>
|
|
<para>
|
|
The following examples use a simple <service-activator />> that always throws an exception:
|
|
<programlisting language="java"><![CDATA[public class FailingService {
|
|
|
|
public void service(String message) {
|
|
throw new RuntimeException("foo");
|
|
}
|
|
}]]></programlisting>
|
|
</para>
|
|
<para><emphasis>Simple Stateless Retry</emphasis></para>
|
|
<para>
|
|
This example uses the default <emphasis>RetryTemplate</emphasis> which has a
|
|
<emphasis>SimpleRetryPolicy</emphasis> which tries 3 times. There is no <emphasis>BackoffPolicy</emphasis>
|
|
so the 3 attempts are made back-to-back-to-back with no delay between attempts. There is no
|
|
<emphasis>RecoveryCallback</emphasis> so, the result is to throw the
|
|
exception to the caller after the final failed retry occurs. In a <emphasis>Spring Integration</emphasis>
|
|
environment, this final exception might be handled using an <emphasis>error-channel</emphasis> on
|
|
the inbound endpoint.
|
|
</para>
|
|
<para><programlisting language="xml"><![CDATA[<int:service-activator input-channel="input" ref="failer" method="service">
|
|
<int:request-handler-advice-chain>
|
|
<bean class="o.s.i.handler.advice.RequestHandlerRetryAdvice"/>
|
|
</request-handler-advice-chain>
|
|
</int:service-activator>
|
|
|
|
DEBUG [task-scheduler-2]preSend on channel 'input', message: [Payload=...]
|
|
DEBUG [task-scheduler-2]Retry: count=0
|
|
DEBUG [task-scheduler-2]Checking for rethrow: count=1
|
|
DEBUG [task-scheduler-2]Retry: count=1
|
|
DEBUG [task-scheduler-2]Checking for rethrow: count=2
|
|
DEBUG [task-scheduler-2]Retry: count=2
|
|
DEBUG [task-scheduler-2]Checking for rethrow: count=3
|
|
DEBUG [task-scheduler-2]Retry failed last attempt: count=3]]></programlisting></para>
|
|
<para><emphasis>Simple Stateless Retry with Recovery</emphasis></para>
|
|
<para>
|
|
This example adds a <classname>RecoveryCallback</classname> to the
|
|
above example; it uses a <classname>ErrorMessageSendingRecoverer</classname>
|
|
to send an <emphasis>ErrorMessage</emphasis> to a channel.
|
|
</para>
|
|
<para><programlisting language="xml"><![CDATA[<int:service-activator input-channel="input" ref="failer" method="service">
|
|
<int:request-handler-advice-chain>
|
|
<bean class="o.s.i.handler.advice.RequestHandlerRetryAdvice">
|
|
<property name="recoveryCallback">
|
|
<bean class="o.s.i.handler.advice.ErrorMessageSendingRecoverer">
|
|
<constructor-arg ref="myErrorChannel" />
|
|
</bean>
|
|
</property>
|
|
</bean>
|
|
</request-handler-advice-chain>
|
|
</int:int:service-activator>
|
|
|
|
DEBUG [task-scheduler-2]preSend on channel 'input', message: [Payload=...]
|
|
DEBUG [task-scheduler-2]Retry: count=0
|
|
DEBUG [task-scheduler-2]Checking for rethrow: count=1
|
|
DEBUG [task-scheduler-2]Retry: count=1
|
|
DEBUG [task-scheduler-2]Checking for rethrow: count=2
|
|
DEBUG [task-scheduler-2]Retry: count=2
|
|
DEBUG [task-scheduler-2]Checking for rethrow: count=3
|
|
DEBUG [task-scheduler-2]Retry failed last attempt: count=3
|
|
DEBUG [task-scheduler-2]Sending ErrorMessage :failedMessage:[Payload=...]]]></programlisting></para>
|
|
<para><emphasis>Stateless Retry with Customized Policies, and Recovery</emphasis></para>
|
|
<para>
|
|
For more sophistication, we can provide the advice with a customized <emphasis>RetryTemplate</emphasis>.
|
|
This example continues to use the <classname>SimpleRetryPolicy</classname> but it
|
|
increases the attempts to 4. It also adds an <classname>ExponentialBackoffPolicy</classname>
|
|
where the first retry waits 1 second, the second waits 5 seconds and the third waits 25 (for 4
|
|
attempts in all).
|
|
</para>
|
|
<para><programlisting language="xml"><![CDATA[<int:service-activator input-channel="input" ref="failer" method="service">
|
|
<int:request-handler-advice-chain>
|
|
<bean class="o.s.i.handler.advice.RequestHandlerRetryAdvice">
|
|
<property name="recoveryCallback">
|
|
<bean class="o.s.i.handler.advice.ErrorMessageSendingRecoverer">
|
|
<constructor-arg ref="myErrorChannel" />
|
|
</bean>
|
|
</property>
|
|
<property name="retryTemplate" ref="retryTemplate" />
|
|
</bean>
|
|
</request-handler-advice-chain>
|
|
</int:service-activator>
|
|
|
|
<bean id="retryTemplate" class="org.springframework.retry.support.RetryTemplate">
|
|
<property name="retryPolicy">
|
|
<bean class="org.springframework.retry.policy.SimpleRetryPolicy">
|
|
<property name="maxAttempts" value="4" />
|
|
</bean>
|
|
</property>
|
|
<property name="backOffPolicy">
|
|
<bean class="org.springframework.retry.backoff.ExponentialBackOffPolicy">
|
|
<property name="initialInterval" value="1000" />
|
|
<property name="multiplier" value="5" />
|
|
</bean>
|
|
</property>
|
|
</bean>
|
|
|
|
27.058 DEBUG [task-scheduler-1]preSend on channel 'input', message: [Payload=...]
|
|
27.071 DEBUG [task-scheduler-1]Retry: count=0
|
|
27.080 DEBUG [task-scheduler-1]Sleeping for 1000
|
|
28.081 DEBUG [task-scheduler-1]Checking for rethrow: count=1
|
|
28.081 DEBUG [task-scheduler-1]Retry: count=1
|
|
28.081 DEBUG [task-scheduler-1]Sleeping for 5000
|
|
33.082 DEBUG [task-scheduler-1]Checking for rethrow: count=2
|
|
33.082 DEBUG [task-scheduler-1]Retry: count=2
|
|
33.083 DEBUG [task-scheduler-1]Sleeping for 25000
|
|
58.083 DEBUG [task-scheduler-1]Checking for rethrow: count=3
|
|
58.083 DEBUG [task-scheduler-1]Retry: count=3
|
|
58.084 DEBUG [task-scheduler-1]Checking for rethrow: count=4
|
|
58.084 DEBUG [task-scheduler-1]Retry failed last attempt: count=4
|
|
58.086 DEBUG [task-scheduler-1]Sending ErrorMessage :failedMessage:[Payload=...]]]></programlisting></para>
|
|
<para><emphasis>Simple Stateful Retry with Recovery</emphasis></para>
|
|
<para>
|
|
To make retry stateful, we need to provide the Advice with a RetryStateGenerator
|
|
implementation. This class is used to identify a message as being a resubmission
|
|
so that the <emphasis>RetryTemplate</emphasis> can determine the current state of retry
|
|
for this message. The framework provides a <classname>SpelExpressionRetryStateGenerator</classname>
|
|
which determines the message identifier using a SpEL expression.
|
|
This is shown below; this example again uses the default policies (3 attempts with no back off); of
|
|
course, as with stateless retry, these policies can be customized.
|
|
</para>
|
|
<para><programlisting language="xml"><![CDATA[<int:service-activator input-channel="input" ref="failer" method="service">
|
|
<int:request-handler-advice-chain>
|
|
<bean class="o.s.i.handler.advice.RequestHandlerRetryAdvice">
|
|
<property name="retryStateGenerator">
|
|
<bean class="o.s.i.handler.advice.SpelExpressionRetryStateGenerator">
|
|
<constructor-arg value="headers['jms_messageId']" />
|
|
</bean>
|
|
</property>
|
|
<property name="recoveryCallback">
|
|
<bean class="o.s.i.handler.advice.ErrorMessageSendingRecoverer">
|
|
<constructor-arg ref="myErrorChannel" />
|
|
</bean>
|
|
</property>
|
|
</bean>
|
|
</int:request-handler-advice-chain>
|
|
</int:service-activator>
|
|
|
|
24.351 DEBUG [Container#0-1]preSend on channel 'input', message: [Payload=...]
|
|
24.368 DEBUG [Container#0-1]Retry: count=0
|
|
24.387 DEBUG [Container#0-1]Checking for rethrow: count=1
|
|
24.387 DEBUG [Container#0-1]Rethrow in retry for policy: count=1
|
|
24.387 WARN [Container#0-1]failure occurred in gateway sendAndReceive
|
|
org.springframework.integration.MessagingException: Failed to invoke handler
|
|
...
|
|
Caused by: java.lang.RuntimeException: foo
|
|
...
|
|
24.391 DEBUG [Container#0-1]Initiating transaction rollback on application exception
|
|
...
|
|
25.412 DEBUG [Container#0-1]preSend on channel 'input', message: [Payload=...]
|
|
25.412 DEBUG [Container#0-1]Retry: count=1
|
|
25.413 DEBUG [Container#0-1]Checking for rethrow: count=2
|
|
25.413 DEBUG [Container#0-1]Rethrow in retry for policy: count=2
|
|
25.413 WARN [Container#0-1]failure occurred in gateway sendAndReceive
|
|
org.springframework.integration.MessagingException: Failed to invoke handler
|
|
...
|
|
Caused by: java.lang.RuntimeException: foo
|
|
...
|
|
25.414 DEBUG [Container#0-1]Initiating transaction rollback on application exception
|
|
...
|
|
26.418 DEBUG [Container#0-1]preSend on channel 'input', message: [Payload=...]
|
|
26.418 DEBUG [Container#0-1]Retry: count=2
|
|
26.419 DEBUG [Container#0-1]Checking for rethrow: count=3
|
|
26.419 DEBUG [Container#0-1]Rethrow in retry for policy: count=3
|
|
26.419 WARN [Container#0-1]failure occurred in gateway sendAndReceive
|
|
org.springframework.integration.MessagingException: Failed to invoke handler
|
|
...
|
|
Caused by: java.lang.RuntimeException: foo
|
|
...
|
|
26.420 DEBUG [Container#0-1]Initiating transaction rollback on application exception
|
|
...
|
|
27.425 DEBUG [Container#0-1]preSend on channel 'input', message: [Payload=...]
|
|
27.426 DEBUG [Container#0-1]Retry failed last attempt: count=3
|
|
27.426 DEBUG [Container#0-1]Sending ErrorMessage :failedMessage:[Payload=...]]]></programlisting></para>
|
|
<para>
|
|
Comparing with the stateless examples, you can see that with stateful retry, the
|
|
exception is thrown to the caller on each failure.
|
|
</para>
|
|
</section>
|
|
</section>
|
|
<section id="circuit-breaker-advice">
|
|
<title>Circuit Breaker Advice</title>
|
|
<para>
|
|
The general idea of the Circuit Breaker Pattern is that, if a service is not currently available, then
|
|
don't waste time (and resources) trying to use it. The
|
|
<classname>o.s.i.handler.advice.RequestHandlerCircuitBreakerAdvice</classname>
|
|
implements this pattern. When the circuit breaker is in the <emphasis>closed</emphasis> state,
|
|
the endpoint will attempt to invoke the
|
|
service. The circuit breaker goes to the <emphasis>open</emphasis> state
|
|
if a certain number of consecutive attempts fail; when it is in the <emphasis>open</emphasis> state, new requests will
|
|
"fail fast" and no attempt will be made to invoke the service until some time has expired.
|
|
</para>
|
|
<para>
|
|
When that time has expired, the circuit breaker is set to the <emphasis>half-open</emphasis> state. When in this state,
|
|
if even a single attempt fails, the breaker will immediately
|
|
go to the <emphasis>open</emphasis> state; if the attempt succeeds, the breaker will go to the
|
|
<emphasis>closed</emphasis> state,
|
|
in which case, it won't go to the <emphasis>open</emphasis> state again until the configured number of consecutive failures
|
|
again occur. Any successful attempt resets the state to zero failures for the purpose of determining when the
|
|
breaker might go to the <emphasis>open</emphasis> state again.
|
|
</para>
|
|
<para>
|
|
Typically, this Advice might be used for external services, where it might take some
|
|
time to fail (such as a timeout attempting to make a network connection).
|
|
</para>
|
|
<para>The <classname>RequestHandlerCircuitBreakerAdvice</classname> has two properties:
|
|
<classname>threshold</classname> and <classname>halfOpenAfter</classname>. The <emphasis>threshold</emphasis>
|
|
property represents the number of consecutive failures that need to occur before the breaker goes
|
|
<emphasis>open</emphasis>. It defaults to 5. The <emphasis>halfOpenAfter</emphasis> property represents
|
|
the time after the last failure that the breaker will wait before attempting another request. Default is
|
|
1000 milliseconds.
|
|
</para>
|
|
<para>Example:</para>
|
|
<para><programlisting language="xml"><![CDATA[<int:service-activator input-channel="input" ref="failer" method="service">
|
|
<int:request-handler-advice-chain>
|
|
<bean class="o.s.i.handler.advice.RequestHandlerCircuitBreakerAdvice">
|
|
<property name="threshold" value="2" />
|
|
<property name="halfOpenAfter" value="12000" />
|
|
</bean>
|
|
</int:request-handler-advice-chain>
|
|
</int:service-activator>
|
|
|
|
05.617 DEBUG [task-scheduler-1]preSend on channel 'input', message: [Payload=...]
|
|
05.638 ERROR [task-scheduler-1]org.springframework.integration.MessageHandlingException: java.lang.RuntimeException: foo
|
|
...
|
|
10.598 DEBUG [task-scheduler-2]preSend on channel 'input', message: [Payload=...]
|
|
10.600 ERROR [task-scheduler-2]org.springframework.integration.MessageHandlingException: java.lang.RuntimeException: foo
|
|
...
|
|
15.598 DEBUG [task-scheduler-3]preSend on channel 'input', message: [Payload=...]
|
|
15.599 ERROR [task-scheduler-3]org.springframework.integration.MessagingException: Circuit Breaker is Open for ServiceActivator
|
|
...
|
|
20.598 DEBUG [task-scheduler-2]preSend on channel 'input', message: [Payload=...]
|
|
20.598 ERROR [task-scheduler-2]org.springframework.integration.MessagingException: Circuit Breaker is Open for ServiceActivator
|
|
...
|
|
25.598 DEBUG [task-scheduler-5]preSend on channel 'input', message: [Payload=...]
|
|
25.601 ERROR [task-scheduler-5]org.springframework.integration.MessageHandlingException: java.lang.RuntimeException: foo
|
|
...
|
|
30.598 DEBUG [task-scheduler-1]preSend on channel 'input', message: [Payload=foo...]
|
|
30.599 ERROR [task-scheduler-1]org.springframework.integration.MessagingException: Circuit Breaker is Open for ServiceActivator]]></programlisting></para>
|
|
<para>
|
|
In the above example, the threshold is set to 2 and halfOpenAfter is set to 12 seconds; a
|
|
new request arrives every 5 seconds. You can see that
|
|
the first two attempts invoked the service; the third and fourth failed with an exception indicating the
|
|
circuit breaker is open. The fifth request was attempted because the request was 15 seconds after the last
|
|
failure; the sixth attempt fails immediately because the breaker immediately went to <emphasis>open</emphasis>.
|
|
</para>
|
|
</section>
|
|
<section id="expression-advice">
|
|
<title>Expression Evaluating Advice</title>
|
|
<para>
|
|
The final supplied advice class is the
|
|
<classname>o.s.i.handler.advice.ExpressionEvaluatingRequestHandlerAdvice</classname>.
|
|
This advice is more general than the other two advices. It provides a mechanism to evaluate an expression on the
|
|
original inbound message sent to the endpoint. Separate expressions are available to be evaluated, either after
|
|
success, or failure. Optionally, a message containing the evaluation result, together with the input message,
|
|
can be sent to a message channel.
|
|
</para>
|
|
<para>
|
|
A typical use case for this advice might be with an <ftp:outbound-channel-adapter />, perhaps to move
|
|
the file to one directory if the transfer was successful, or to another directory if it fails:
|
|
</para>
|
|
<para>
|
|
The Advice has properties to set an expression when successful, an expression for failures,
|
|
and corresponding channels for each. For the successful case, the message sent to the
|
|
<emphasis>successChannel</emphasis> is an <classname>AdviceMessage</classname>, with
|
|
the payload being the result of the expression evaluation, and an additional property
|
|
<code>inputMessage</code> which contains the original message sent to the handler. A message
|
|
sent to the <emphasis>failureChannel</emphasis> (when the handler throws an excecption)
|
|
is an ErrorMessage with a payload of <classname>MessageHandlingExpressionEvaluatingAdviceException</classname>.
|
|
Like all <classname>MessagingException</classname>s, this payload has <code>failedMessage</code>
|
|
and <code>cause</code> properties, as well as an additional property <code>evaluationResult</code>,
|
|
containing the result of the expression evaluation.
|
|
</para>
|
|
</section>
|
|
</section>
|
|
<section id="custom-advice">
|
|
<title>Custom Advice Classes</title>
|
|
<para>
|
|
In addition to the provided Advice classes above, you can implement your own Advice classes. While you can
|
|
provide any implementation of <classname>org.aopalliance.aop.Advice</classname>, it is generally recommended
|
|
that you subclass <classname>o.s.i.handler.advice.AbstractRequestHandlerAdvice</classname>.
|
|
This has the benefit of avoiding writing low-level <emphasis>Aspect Oriented Programming</emphasis> code as well
|
|
as providing a starting point that is specifically tailored for use in this environment.
|
|
</para>
|
|
<para>
|
|
Subclasses need to implement the doInvoke() method:
|
|
</para>
|
|
<para><programlisting language="java"><![CDATA[/**
|
|
* Subclasses implement this method to apply behavior to the {@link MessageHandler} callback.execute()
|
|
* invokes the handler method and returns its result, or null).
|
|
* @param callback Subclasses invoke the execute() method on this interface to invoke the handler method.
|
|
* @param target The target handler.
|
|
* @param message The message that will be sent to the handler.
|
|
* @return the result after invoking the {@link MessageHandler}.
|
|
* @throws Exception
|
|
*/
|
|
protected abstract Object doInvoke(ExecutionCallback callback, Object target, Message<?> message) throws Exception;]]></programlisting>
|
|
</para>
|
|
<para>
|
|
The <emphasis>callback</emphasis> parameter is simply a convenience to avoid subclasses dealing with AOP directly; invoking the
|
|
<classname>callback.execute()</classname>
|
|
method invokes the message handler.
|
|
</para>
|
|
<para>
|
|
The <emphasis>target</emphasis> parameter is provided for those subclasses that need to maintain state for a
|
|
specific handler, perhaps by maintaining that state in a <classname>Map</classname>, keyed by the target.
|
|
This allows the same advice to be applied to multiple handlers. The
|
|
<classname>RequestHandlerCircuitBreakerAdvice</classname> uses this to
|
|
keep circuit breaker state for each handler.
|
|
</para>
|
|
<para>
|
|
The <emphasis>message</emphasis> parameter is the message that will be sent to the handler.
|
|
While the advice cannot modify the message
|
|
before invoking the handler, it can modify the payload (if it has mutable properties). Typically, an advice would
|
|
use the message for logging and/or to send a copy of the message somewhere before or after invoking the
|
|
handler.
|
|
</para>
|
|
<para>
|
|
The return value would normally be the value returned by <classname>callback.execute()</classname>;
|
|
but the advice does have the
|
|
ability to modify the return value. Note that only <classname>AbstractReplyProducingMessageHandler</classname>s
|
|
return a value.
|
|
</para>
|
|
<para><programlisting language="java"><![CDATA[public class MyAdvice extends AbstractRequestHandlerAdvice {
|
|
|
|
@Override
|
|
protected Object doInvoke(ExecutionCallback callback, Object target, Message<?> message) throws Exception {
|
|
// add code before the invocation
|
|
Object result = callback.execute();
|
|
// add code after the invocation
|
|
return result;
|
|
}
|
|
}]]></programlisting></para>
|
|
<note>
|
|
<para>
|
|
In addition to the <code>execute()</code> method, the <interfacename>ExecutionCallback</interfacename> provides
|
|
an additional method <code>cloneAndExecute()</code>. This method must be used in cases where the invocation
|
|
might be called multiple times within a single execution of <code>doInvoke()</code>, such as in the
|
|
<classname>RequestHandlerRetryAdvice</classname>. This is required because the Spring AOP
|
|
<classname>org.springframework.aop.framework.ReflectiveMethodInvocation</classname> object maintains state of which
|
|
advice in a chain was last invoked; this state must be reset for each call.
|
|
</para>
|
|
<para>
|
|
For more information, see the
|
|
<ulink url="http://static.springsource.org/spring-framework/docs/current/javadoc-api/org/springframework/aop/framework/ReflectiveMethodInvocation.html">
|
|
ReflectiveMethodInvocation</ulink> JavaDocs.
|
|
</para>
|
|
</note>
|
|
</section>
|
|
<section id="other-advice">
|
|
<title>Other Advice Chain Elements</title>
|
|
<para>
|
|
While the abstract class mentioned above is provided as a convenience, you can add any <classname>
|
|
Advice</classname> to the chain, including a transaction advice.
|
|
</para>
|
|
</section>
|
|
<section id="advising-filters">
|
|
<title>Advising Filters</title>
|
|
<para>
|
|
There is an additional consideration when advising <classname>Filter</classname>s. By default, any discard
|
|
actions (when the filter returns false) are performed <emphasis>within</emphasis> the scope of the
|
|
advice chain. This could include all the flow downstream of the <emphasis>discard channel</emphasis>.
|
|
So, for example if an element downstream of the <emphasis>discard-channel</emphasis> throws an exception,
|
|
and there is a retry advice, the process will be retried. This is also the case if
|
|
<emphasis>throwExceptionOnRejection</emphasis> is set to true (the exception is thrown within the
|
|
scope of the advice).
|
|
</para>
|
|
<para>
|
|
Setting <emphasis>discard-within-advice</emphasis> to "false" modifies this behavior and the discard
|
|
(or exception) occurs after the advice chain is called.
|
|
</para>
|
|
</section>
|
|
<section id="advising-with-annotations">
|
|
<title>Advising Endpoints Using Annotations</title>
|
|
<para>
|
|
When configuring certain endpoints using annotations (<code>@Filter</code>, <code>@ServiceActivator</code>,
|
|
<code>@Splitter</code>, and <code>@Transformer</code>), you can supply a bean name for the advice
|
|
chain in the <code>adviceChain</code> attribute. In addition, the <code>@Filter</code> annotation
|
|
also has the <code>discardWithinAdvice</code> attribute, which can be used to configure the discard
|
|
behavior as discussed in <xref linkend="advising-filters"/>. An example with the discard being
|
|
performed after the advice is shown below.
|
|
</para>
|
|
<programlisting language="java"><![CDATA[@MessageEndpoint
|
|
public class MyAdvisedFilter {
|
|
|
|
@Filter(inputChannel="input", outputChannel="output",
|
|
adviceChain="adviceChain", discardWithinAdvice="false")
|
|
public boolean filter(String s) {
|
|
return s.contains("good");
|
|
}
|
|
}]]></programlisting>
|
|
</section>
|
|
<section id="Advice Order">
|
|
<title>Ordering Advices within an Advice Chain</title>
|
|
<para>
|
|
Advice classes are "around" advices and are applied in a nested fashion. The first advice is the
|
|
outermost, the last advice the innermost (closest to the handler being advised). It is important
|
|
to put the advice classes in the correct order to achieve the functionality you desire.
|
|
</para>
|
|
<para>
|
|
For example, let's say you want to add a retry advice and a transaction advice.
|
|
You may want to place the retry advice advice first, followed by the transaction advice.
|
|
Then, each retry will be performed in a new transaction. On the other hand, if you want all the attempts,
|
|
and any recovery operations (in the retry <classname>RecoveryCallback</classname>), to be scoped within
|
|
the transaction, you would put the transaction advice first.
|
|
</para>
|
|
</section>
|
|
</section>
|