INT-2698 Reference Docs for Handler Advice
INT-2698 Doc Retry Initial Commit INT-2698 Doc Circuit Breaker INT-2698 EERHA INT-2698 Custom Advice INT-2698 Polishing INT-2698 Polishing (PR Review)
This commit is contained in:
@@ -23,9 +23,14 @@ import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.core.MessageHandler;
|
||||
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
|
||||
|
||||
/**
|
||||
* Base class for {@link MessageHandler} advice classes.
|
||||
* Base class for {@link MessageHandler} advice classes. Subclasses should provide
|
||||
* an implementation for {@link #doInvoke(ExecutionCallback, Object, Message)}.
|
||||
* Used to advise the handleRequestMessage method for {@link AbstractReplyProducingMessageHandler} or
|
||||
* {@link MessageHandler#handleMessage(Message)} for other message handlers.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @since 2.2
|
||||
*
|
||||
@@ -70,6 +75,15 @@ public abstract class AbstractRequestHandlerAdvice implements MethodInterceptor
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses implement this method to apply behavior to the {@link MessageHandler}.<p/> 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;
|
||||
|
||||
protected interface ExecutionCallback {
|
||||
|
||||
458
src/reference/docbook/handler-advice.xml
Normal file
458
src/reference/docbook/handler-advice.xml
Normal file
@@ -0,0 +1,458 @@
|
||||
<?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><![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>
|
||||
<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>MessageHandlerRetryAdvice</listitem>
|
||||
<listitem>MessageHandlerCircuitBreakerAdvice</listitem>
|
||||
<listitem>ExpressionEvaluatingMessageHandlerAdvice</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>org.springframework.integration.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><![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><![CDATA[<int:service-activator input-channel="input" ref="failer" method="service">
|
||||
<int:request-handler-advice-chain>
|
||||
<bean class="org.springframework.integration.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></classname>
|
||||
to send an <emphasis>ErrorMessage</emphasis> to a channel.
|
||||
</para>
|
||||
<para><programlisting><![CDATA[<int:service-activator input-channel="input" ref="failer" method="service">
|
||||
<int:request-handler-advice-chain>
|
||||
<bean class="org.springframework.integration.handler.advice.RequestHandlerRetryAdvice">
|
||||
<property name="recoveryCallback">
|
||||
<bean class="org.springframework.integration.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><![CDATA[<int:service-activator input-channel="input" ref="failer" method="service">
|
||||
<int:request-handler-advice-chain>
|
||||
<bean class="org.springframework.integration.handler.advice.RequestHandlerRetryAdvice">
|
||||
<property name="recoveryCallback">
|
||||
<bean class="org.springframework.integration.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><![CDATA[<int:service-activator input-channel="input" ref="failer" method="service">
|
||||
<int:request-handler-advice-chain>
|
||||
<bean class="org.springframework.integration.handler.advice.RequestHandlerRetryAdvice">
|
||||
<property name="retryStateGenerator">
|
||||
<bean class="org.springframework.integration.handler.advice.SpelExpressionRetryStateGenerator">
|
||||
<constructor-arg value="headers['jms_messageId']" />
|
||||
</bean>
|
||||
</property>
|
||||
<property name="recoveryCallback">
|
||||
<bean class="org.springframework.integration.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>org.springframework.integration.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><![CDATA[<int:service-activator input-channel="input" ref="failer" method="service">
|
||||
<int:request-handler-advice-chain>
|
||||
<bean class="org.springframework.integration.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>org.springframework.integration.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, the original message, together with the result of the evaluation in a
|
||||
header, 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><programlisting><![CDATA[<int-ftp:outbound-channel-adapter id="ftpOutbound" cache-sessions="false"
|
||||
channel="ftpChannel"
|
||||
remote-directory="."
|
||||
session-factory="ftpClientFactory">
|
||||
<int-ftp:request-handler-advice-chain>
|
||||
<bean class="org.springframework.integration.handler.advice.ExpressionEvaluatingRequestHandlerAdvice">
|
||||
<constructor-arg name="onSuccessExpression" value="payload.renameTo('/tmp/good/' + payload.name)"/>
|
||||
<constructor-arg name="successChannel" ref="successChannel" />
|
||||
<constructor-arg name="onFailureExpression" value="payload.renameTo('/tmp/bad/' + payload.name)"/>
|
||||
<constructor-arg name="failureChannel" ref="failureChannel" />
|
||||
</bean>
|
||||
</int-ftp:request-handler-advice-chain>
|
||||
</int-ftp:outbound-channel-adapter>
|
||||
|
||||
|
||||
DEBUG [main] preSend on channel 'ftpChannel', message: [Payload=target/toSend/b.txt][Headers=...]
|
||||
DEBUG [main] Connected to server [localhost:21]
|
||||
INFO [main] File has been successfully transfered to: ./b.txt.writing
|
||||
INFO [main] File has been successfully renamed from: ./b.txt.writing to ./b.txt
|
||||
DEBUG [main] preSend on channel 'successChannel', message: [Payload=target/toSend/b.txt][Headers={..., postProcessResult=true}]
|
||||
|
||||
...
|
||||
|
||||
DEBUG [main] preSend on channel 'ftpChannel', message: [Payload=target/toSend/a.txt][Headers=...]
|
||||
DEBUG [main] Connected to server [localhost:21]
|
||||
...
|
||||
DEBUG [main] preSend on channel 'failureChannel', message: [Payload=target/toSend/a.txt][Headers={..., postProcessResult=true}]]]></programlisting></para>
|
||||
<para>
|
||||
As you can see, in the first case, the successful transfer resulted in a message sent to
|
||||
<emphasis>successChannel</emphasis>; the second case shows the result being sent to
|
||||
<emphasis>failureChannel</emphasis>. In both cases, you can see the <emphasis>postProcessResult</emphasis>
|
||||
header containing the result of the evaluation (true because the rename operations were successful -
|
||||
<classname>java.io.File.renameTo(...)</classname> returns a <emphasis>boolean</emphasis>).
|
||||
</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 Adivce classes. While you can
|
||||
provide any implementation of <classname>org.aopalliance.aop.Advice</classname>, it is generally recommended
|
||||
that you subclass <classname>org.springframework.integration.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><![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><![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>
|
||||
</section>
|
||||
</section>
|
||||
@@ -10,5 +10,6 @@
|
||||
<xi:include href="./delayer.xml"/>
|
||||
<xi:include href="./scripting.xml"/>
|
||||
<xi:include href="./groovy.xml"/>
|
||||
<xi:include href="./handler-advice.xml"/>
|
||||
|
||||
</chapter>
|
||||
|
||||
@@ -117,6 +117,27 @@
|
||||
messages to complete.
|
||||
</para>
|
||||
</section>
|
||||
<section id="2.2-handler-advice">
|
||||
<title>Adding Behavior to Endpoints</title>
|
||||
<para>
|
||||
The ability to add an <advice-chain/> to a poller has been available for some time.
|
||||
However, the behavior added by this affects the entire integration flow.
|
||||
It did not address the ability to add, say, retry, to an individual
|
||||
endpoint. The 2.2. release introduces the <request-handler-advice-chain/>
|
||||
to many endpoints.
|
||||
</para>
|
||||
<para>
|
||||
In addition, 3 standard Advice classes have been provided for this purpose:
|
||||
</para>
|
||||
<itemizedlist>
|
||||
<listitem>MessageHandlerRetryAdvice</listitem>
|
||||
<listitem>MessageHandlerCircuitBreakerAdvice</listitem>
|
||||
<listitem>ExpressionEvaluatingMessageHandlerAdvice</listitem>
|
||||
</itemizedlist>
|
||||
<para>
|
||||
For more information, see <xref linkend="message-handler-advice-chain"/>.
|
||||
</para>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section id="2.2-new-components">
|
||||
|
||||
Reference in New Issue
Block a user