diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/advice/AbstractRequestHandlerAdvice.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/advice/AbstractRequestHandlerAdvice.java
index 4f17c89d7f..a471ec24a6 100644
--- a/spring-integration-core/src/main/java/org/springframework/integration/handler/advice/AbstractRequestHandlerAdvice.java
+++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/advice/AbstractRequestHandlerAdvice.java
@@ -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}.
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 {
diff --git a/src/reference/docbook/handler-advice.xml b/src/reference/docbook/handler-advice.xml
new file mode 100644
index 0000000000..89bb7ec1e3
--- /dev/null
+++ b/src/reference/docbook/handler-advice.xml
@@ -0,0 +1,458 @@
+
+
+ Adding Behavior to Endpoints
+
+
+ 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.
+
+
+ For example, consider the following flow:
+
+
+ inbound-adapter->poller->http-gateway1->http-gateway2->jdbc-outbound-adapter
+
+
+ If you configure some retry-logic into an advice chain on the poller, and, the call to
+ http-gateway2 failed because of a network glitch, the retry would cause
+ both http-gateway1 and http-gateway2 to be called
+ a second time. Similarly, after a transient failure in the
+ jdbc-outbound-adapter, both http-gateways
+ would be called a second time before again calling the jdbc-outbound-adapter.
+
+
+ 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:
+
+
+
+
+
+]]>
+
+ In this case, myRetryAdvice will only be applied locally to this gateway and
+ will not apply to further actions taken downstream after the reply is sent to the
+ nextChannel. The scope of the advice is limited to the endpoint itself.
+
+
+ Provided Advice Classes
+
+ In addition to providing the general mechanism to apply AOP Advice classes in this way, three
+ standard Advices are provided:
+
+
+ MessageHandlerRetryAdvice
+ MessageHandlerCircuitBreakerAdvice
+ ExpressionEvaluatingMessageHandlerAdvice
+
+
+ These are each described in detail in the following sections.
+
+
+ Retry Advice
+
+ The retry advice (org.springframework.integration.handler.advice.RequestHandlerRetryAdvice)
+ leverages the rich retry mechanisms provided by the
+ spring-retry project. The core component
+ of spring-retry is the RetryTemplate, which allows configuration
+ of sophisticated retry scenarios, including RetryPolicy and BackoffPolicy
+ strategies, with a number of implementations,
+ as well as a RecoveryCallback strategy to determine the action to take when retries
+ are exhausted.
+
+ Stateless Retry
+
+ 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.
+
+ Stateful Retry
+
+ 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.
+
+ Further Information
+
+ For more information on spring-retry, refer to the project's javadocs, as well as the
+ reference documentation for
+ Spring Batch, where spring-retry originated.
+
+
+ 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.
+
+
+ Configuring the Retry Advice
+
+ The following examples use a simple <service-activator />> that always throws an exception:
+
+
+ Simple Stateless Retry
+
+ This example uses the default RetryTemplate which has a
+ SimpleRetryPolicy which tries 3 times. There is no BackoffPolicy
+ so the 3 attempts are made back-to-back-to-back with no delay between attempts. There is no
+ RecoveryCallback so, the result is to throw the
+ exception to the caller after the final failed retry occurs. In a Spring Integration
+ environment, this final exception might be handled using an error-channel on
+ the inbound endpoint.
+
+
+
+
+
+
+
+
+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]]>
+ Simple Stateless Retry with Recovery
+
+ This example adds a RecoveryCallback to the
+ above example; it uses a
+ to send an ErrorMessage to a channel.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+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=...]]]>
+ Stateless Retry with Customized Policies, and Recovery
+
+ For more sophistication, we can provide the advice with a customized RetryTemplate.
+ This example continues to use the SimpleRetryPolicy but it
+ increases the attempts to 4. It also adds an ExponentialBackoffPolicy
+ where the first retry waits 1 second, the second waits 5 seconds and the third waits 25 (for 4
+ attempts in all).
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+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=...]]]>
+ Simple Stateful Retry with Recovery
+
+ 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 RetryTemplate can determine the current state of retry
+ for this message. The framework provides a SpelExpressionRetryStateGenerator
+ 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.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+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=...]]]>
+
+ Comparing with the stateless examples, you can see that with stateful retry, the
+ exception is thrown to the caller on each failure.
+
+
+
+
+ Circuit Breaker Advice
+
+ 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
+ org.springframework.integration.handler.advice.RequestHandlerCircuitBreakerAdvice
+ implements this pattern. When the circuit breaker is in the closed state,
+ the endpoint will attempt to invoke the
+ service. The circuit breaker goes to the open state
+ if a certain number of consecutive attempts fail; when it is in the open state, new requests will
+ "fail fast" and no attempt will be made to invoke the service until some time has expired.
+
+
+ When that time has expired, the circuit breaker is set to the half-open state. When in this state,
+ if even a single attempt fails, the breaker will immediately
+ go to the open state; if the attempt succeeds, the breaker will go to the
+ closed state,
+ in which case, it won't go to the open 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 open state again.
+
+
+ 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).
+
+ The RequestHandlerCircuitBreakerAdvice has two properties:
+ threshold and halfOpenAfter. The threshold
+ property represents the number of consecutive failures that need to occur before the breaker goes
+ open. It defaults to 5. The halfOpenAfter property represents
+ the time after the last failure that the breaker will wait before attempting another request. Default is
+ 1000 milliseconds.
+
+ Example:
+
+
+
+
+
+
+
+
+
+
+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]]>
+
+ 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 open.
+
+
+
+ Expression Evaluating Advice
+
+ The final supplied advice class is the
+ org.springframework.integration.handler.advice.ExpressionEvaluatingRequestHandlerAdvice.
+ 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.
+
+
+ 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:
+
+
+
+
+
+
+
+
+
+
+
+
+
+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}]]]>
+
+ As you can see, in the first case, the successful transfer resulted in a message sent to
+ successChannel; the second case shows the result being sent to
+ failureChannel. In both cases, you can see the postProcessResult
+ header containing the result of the evaluation (true because the rename operations were successful -
+ java.io.File.renameTo(...) returns a boolean).
+
+
+
+
+ Custom Advice Classes
+
+ In addition to the provided Advice classes above, you can implement your own Adivce classes. While you can
+ provide any implementation of org.aopalliance.aop.Advice, it is generally recommended
+ that you subclass org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice.
+ This has the benefit of avoiding writing low-level Aspect Oriented Programming code as well
+ as providing a starting point that is specifically tailored for use in this environment.
+
+
+ Subclasses need to implement the doInvoke() method:
+
+ message) throws Exception;]]>
+
+
+ The callback parameter is simply a convenience to avoid subclasses dealing with AOP directly; invoking the
+ callback.execute()
+ method invokes the message handler.
+
+
+ The target parameter is provided for those subclasses that need to maintain state for a
+ specific handler, perhaps by maintaining that state in a Map, keyed by the target.
+ This allows the same advice to be applied to multiple handlers. The
+ RequestHandlerCircuitBreakerAdvice uses this to
+ keep circuit breaker state for each handler.
+
+
+ The message 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.
+
+
+ The return value would normally be the value returned by callback.execute();
+ but the advice does have the
+ ability to modify the return value. Note that only AbstractReplyProducingMessageHandlers
+ return a value.
+
+ message) throws Exception {
+ // add code before the invocation
+ Object result = callback.execute();
+ // add code after the invocation
+ return result;
+ }
+}]]>
+
+
diff --git a/src/reference/docbook/messaging-endpoints.xml b/src/reference/docbook/messaging-endpoints.xml
index ebe7436752..77c1bd7065 100644
--- a/src/reference/docbook/messaging-endpoints.xml
+++ b/src/reference/docbook/messaging-endpoints.xml
@@ -10,5 +10,6 @@
+
diff --git a/src/reference/docbook/whats-new.xml b/src/reference/docbook/whats-new.xml
index 8007382e10..c2f2ce05ad 100644
--- a/src/reference/docbook/whats-new.xml
+++ b/src/reference/docbook/whats-new.xml
@@ -117,6 +117,27 @@
messages to complete.
+
+ Adding Behavior to Endpoints
+
+ 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.
+
+
+ In addition, 3 standard Advice classes have been provided for this purpose:
+
+
+ MessageHandlerRetryAdvice
+ MessageHandlerCircuitBreakerAdvice
+ ExpressionEvaluatingMessageHandlerAdvice
+
+
+ For more information, see .
+
+