Skip to content

Commit 9d2e6c6

Browse files
committed
Remove outdated retry documentation
Spring Retry was pulled out of Spring Batch since 2.2. The documentation of Spring Retry has evolved since then, and the retry section of the batch docs is now out of sync. This commit removes the outdated documentation while keeping the link to Spring Retry.
1 parent f555ca9 commit 9d2e6c6

File tree

1 file changed

+3
-343
lines changed

1 file changed

+3
-343
lines changed

spring-batch-docs/asciidoc/retry.adoc

Lines changed: 3 additions & 343 deletions
Original file line numberDiff line numberDiff line change
@@ -6,357 +6,17 @@
66

77
== Retry
88

9-
ifndef::onlyonetoggle[]
10-
include::toggle.adoc[]
11-
endif::onlyonetoggle[]
12-
139
To make processing more robust and less prone to failure, it sometimes helps to
1410
automatically retry a failed operation in case it might succeed on a subsequent attempt.
1511
Errors that are susceptible to intermittent failure are often transient in nature.
1612
Examples include remote calls to a web service that fails because of a network glitch or a
1713
`DeadlockLoserDataAccessException` in a database update.
1814

19-
[[retryTemplate]]
20-
=== `RetryTemplate`
21-
2215
[NOTE]
2316
====
2417
The retry functionality was pulled out of Spring Batch as of 2.2.0.
2518
It is now part of a new library, https://github.com/spring-projects/spring-retry[Spring Retry].
19+
Spring Batch still relies on Spring Retry to automate retry operations within the framework.
20+
Please refer to the reference documentation of Spring Retry for details about
21+
key APIs and how to use them.
2622
====
27-
28-
To automate retry operations Spring Batch has the `RetryOperations` strategy. The
29-
following interface definition for `RetryOperations`:
30-
31-
[source, java]
32-
----
33-
public interface RetryOperations {
34-
35-
<T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback) throws E;
36-
37-
<T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback, RecoveryCallback<T> recoveryCallback)
38-
throws E;
39-
40-
<T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback, RetryState retryState)
41-
throws E, ExhaustedRetryException;
42-
43-
<T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback, RecoveryCallback<T> recoveryCallback,
44-
RetryState retryState) throws E;
45-
46-
}
47-
----
48-
49-
The basic callback is a simple interface that lets you insert some business logic to be
50-
retried, as shown in the following interface definition:
51-
52-
[source, java]
53-
----
54-
public interface RetryCallback<T, E extends Throwable> {
55-
56-
T doWithRetry(RetryContext context) throws E;
57-
58-
}
59-
----
60-
61-
The callback runs and, if it fails (by throwing an `Exception`), it is retried until
62-
either it is successful or the implementation aborts. There are a number of overloaded
63-
`execute` methods in the `RetryOperations` interface. Those methods deal with various use
64-
cases for recovery when all retry attempts are exhausted and deal with retry state, which
65-
lets clients and implementations store information between calls (we cover this in more
66-
detail later in the chapter).
67-
68-
The simplest general purpose implementation of `RetryOperations` is `RetryTemplate`. It
69-
can be used as follows:
70-
71-
[source, java]
72-
----
73-
RetryTemplate template = new RetryTemplate();
74-
75-
TimeoutRetryPolicy policy = new TimeoutRetryPolicy();
76-
policy.setTimeout(30000L);
77-
78-
template.setRetryPolicy(policy);
79-
80-
Foo result = template.execute(new RetryCallback<Foo>() {
81-
82-
public Foo doWithRetry(RetryContext context) {
83-
// Do stuff that might fail, e.g. webservice operation
84-
return result;
85-
}
86-
87-
});
88-
----
89-
90-
In the preceding example, we make a web service call and return the result to the user. If
91-
that call fails, then it is retried until a timeout is reached.
92-
93-
[[retryContext]]
94-
==== `RetryContext`
95-
96-
The method parameter for the `RetryCallback` is a `RetryContext`. Many callbacks ignore
97-
the context, but, if necessary, it can be used as an attribute bag to store data for the
98-
duration of the iteration.
99-
100-
A `RetryContext` has a parent context if there is a nested retry in progress in the same
101-
thread. The parent context is occasionally useful for storing data that need to be shared
102-
between calls to `execute`.
103-
104-
[[recoveryCallback]]
105-
==== `RecoveryCallback`
106-
107-
When a retry is exhausted, the `RetryOperations` can pass control to a different callback,
108-
called the `RecoveryCallback`. To use this feature, clients pass in the callbacks together
109-
to the same method, as shown in the following example:
110-
111-
[source, java]
112-
----
113-
Foo foo = template.execute(new RetryCallback<Foo>() {
114-
public Foo doWithRetry(RetryContext context) {
115-
// business logic here
116-
},
117-
new RecoveryCallback<Foo>() {
118-
Foo recover(RetryContext context) throws Exception {
119-
// recover logic here
120-
}
121-
});
122-
----
123-
124-
If the business logic does not succeed before the template decides to abort, then the
125-
client is given the chance to do some alternate processing through the recovery callback.
126-
127-
[[statelessRetry]]
128-
==== Stateless Retry
129-
130-
In the simplest case, a retry is just a while loop. The `RetryTemplate` can just keep
131-
trying until it either succeeds or fails. The `RetryContext` contains some state to
132-
determine whether to retry or abort, but this state is on the stack and there is no need
133-
to store it anywhere globally, so we call this stateless retry. The distinction between
134-
stateless and stateful retry is contained in the implementation of the `RetryPolicy` (the
135-
`RetryTemplate` can handle both). In a stateless retry, the retry callback is always
136-
executed in the same thread it was on when it failed.
137-
138-
[[statefulRetry]]
139-
==== Stateful Retry
140-
141-
Where the failure has caused a transactional resource to become invalid, there are some
142-
special considerations. This does not apply to a simple remote call because there is no
143-
transactional resource (usually), but it does sometimes apply to a database update,
144-
especially when using Hibernate. In this case it only makes sense to re-throw the
145-
exception that called the failure immediately, so that the transaction can roll back and
146-
we can start a new, valid transaction.
147-
148-
In cases involving transactions, a stateless retry is not good enough, because the
149-
re-throw and roll back necessarily involve leaving the `RetryOperations.execute()` method
150-
and potentially losing the context that was on the stack. To avoid losing it we have to
151-
introduce a storage strategy to lift it off the stack and put it (at a minimum) in heap
152-
storage. For this purpose, Spring Batch provides a storage strategy called
153-
`RetryContextCache`, which can be injected into the `RetryTemplate`. The default
154-
implementation of the `RetryContextCache` is in memory, using a simple `Map`. Advanced
155-
usage with multiple processes in a clustered environment might also consider implementing
156-
the `RetryContextCache` with a cluster cache of some sort (however, even in a clustered
157-
environment, this might be overkill).
158-
159-
Part of the responsibility of the `RetryOperations` is to recognize the failed operations
160-
when they come back in a new execution (and usually wrapped in a new transaction). To
161-
facilitate this, Spring Batch provides the `RetryState` abstraction. This works in
162-
conjunction with a special `execute` methods in the `RetryOperations` interface.
163-
164-
The way the failed operations are recognized is by identifying the state across multiple
165-
invocations of the retry. To identify the state, the user can provide a `RetryState`
166-
object that is responsible for returning a unique key identifying the item. The identifier
167-
is used as a key in the `RetryContextCache` interface.
168-
169-
[WARNING]
170-
====
171-
Be very careful with the implementation of `Object.equals()` and `Object.hashCode()` in
172-
the key returned by `RetryState`. The best advice is to use a business key to identify the
173-
items. In the case of a JMS message, the message ID can be used.
174-
====
175-
176-
When the retry is exhausted, there is also the option to handle the failed item in a
177-
different way, instead of calling the `RetryCallback` (which is now presumed to be likely
178-
to fail). Just like in the stateless case, this option is provided by the
179-
`RecoveryCallback`, which can be provided by passing it in to the `execute` method of
180-
`RetryOperations`.
181-
182-
The decision to retry or not is actually delegated to a regular `RetryPolicy`, so the
183-
usual concerns about limits and timeouts can be injected there (described later in this
184-
chapter).
185-
186-
[[retryPolicies]]
187-
=== Retry Policies
188-
189-
Inside a `RetryTemplate`, the decision to retry or fail in the `execute` method is
190-
determined by a `RetryPolicy`, which is also a factory for the `RetryContext`. The
191-
`RetryTemplate` has the responsibility to use the current policy to create a
192-
`RetryContext` and pass that in to the `RetryCallback` at every attempt. After a callback
193-
fails, the `RetryTemplate` has to make a call to the `RetryPolicy` to ask it to update its
194-
state (which is stored in the `RetryContext`) and then asks the policy if another attempt
195-
can be made. If another attempt cannot be made (such as when a limit is reached or a
196-
timeout is detected) then the policy is also responsible for handling the exhausted state.
197-
Simple implementations throw `RetryExhaustedException`, which causes any enclosing
198-
transaction to be rolled back. More sophisticated implementations might attempt to take
199-
some recovery action, in which case the transaction can remain intact.
200-
201-
[TIP]
202-
====
203-
Failures are inherently either retryable or not. If the same exception is always going to
204-
be thrown from the business logic, it does no good to retry it. So do not retry on all
205-
exception types. Rather, try to focus on only those exceptions that you expect to be
206-
retryable. It is not usually harmful to the business logic to retry more aggressively, but
207-
it is wasteful, because, if a failure is deterministic, you spend time retrying something
208-
that you know in advance is fatal.
209-
====
210-
211-
Spring Batch provides some simple general purpose implementations of stateless
212-
`RetryPolicy`, such as `SimpleRetryPolicy` and `TimeoutRetryPolicy` (used in the preceding example).
213-
214-
The `SimpleRetryPolicy` allows a retry on any of a named list of exception types, up to a
215-
fixed number of times. It also has a list of "fatal" exceptions that should never be
216-
retried, and this list overrides the retryable list so that it can be used to give finer
217-
control over the retry behavior, as shown in the following example:
218-
219-
[source, java]
220-
----
221-
SimpleRetryPolicy policy = new SimpleRetryPolicy();
222-
// Set the max retry attempts
223-
policy.setMaxAttempts(5);
224-
// Retry on all exceptions (this is the default)
225-
policy.setRetryableExceptions(new Class[] {Exception.class});
226-
// ... but never retry IllegalStateException
227-
policy.setFatalExceptions(new Class[] {IllegalStateException.class});
228-
229-
// Use the policy...
230-
RetryTemplate template = new RetryTemplate();
231-
template.setRetryPolicy(policy);
232-
template.execute(new RetryCallback<Foo>() {
233-
public Foo doWithRetry(RetryContext context) {
234-
// business logic here
235-
}
236-
});
237-
----
238-
239-
There is also a more flexible implementation called `ExceptionClassifierRetryPolicy`,
240-
which lets the user configure different retry behavior for an arbitrary set of exception
241-
types though the `ExceptionClassifier` abstraction. The policy works by calling on the
242-
classifier to convert an exception into a delegate `RetryPolicy`. For example, one
243-
exception type can be retried more times before failure than another by mapping it to a
244-
different policy.
245-
246-
Users might need to implement their own retry policies for more customized decisions. For
247-
instance, a custom retry policy makes sense when there is a well-known, solution-specific
248-
classification of exceptions into retryable and not retryable.
249-
250-
[[backoffPolicies]]
251-
=== Backoff Policies
252-
253-
When retrying after a transient failure, it often helps to wait a bit before trying again,
254-
because usually the failure is caused by some problem that can only be resolved by
255-
waiting. If a `RetryCallback` fails, the `RetryTemplate` can pause execution according to
256-
the `BackoffPolicy`.
257-
258-
The following code shows the interface definition for the `BackOffPolicy` interface:
259-
260-
[source, java]
261-
----
262-
public interface BackoffPolicy {
263-
264-
BackOffContext start(RetryContext context);
265-
266-
void backOff(BackOffContext backOffContext)
267-
throws BackOffInterruptedException;
268-
269-
}
270-
----
271-
272-
A `BackoffPolicy` is free to implement the backOff in any way it chooses. The policies
273-
provided by Spring Batch out of the box all use `Object.wait()`. A common use case is to
274-
backoff with an exponentially increasing wait period, to avoid two retries getting into
275-
lock step and both failing (this is a lesson learned from ethernet). For this purpose,
276-
Spring Batch provides the `ExponentialBackoffPolicy`.
277-
278-
[[retryListeners]]
279-
=== Listeners
280-
281-
Often, it is useful to be able to receive additional callbacks for cross cutting concerns
282-
across a number of different retries. For this purpose, Spring Batch provides the
283-
`RetryListener` interface. The `RetryTemplate` lets users register `RetryListeners`, and
284-
they are given callbacks with `RetryContext` and `Throwable` where available during the
285-
iteration.
286-
287-
The following code shows the interface definition for `RetryListener`:
288-
[source, java]
289-
----
290-
public interface RetryListener {
291-
292-
<T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback);
293-
294-
<T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable);
295-
296-
<T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable);
297-
}
298-
----
299-
300-
The `open` and `close` callbacks come before and after the entire retry in the simplest
301-
case, and `onError` applies to the individual `RetryCallback` calls. The `close` method
302-
might also receive a `Throwable`. If there has been an error, it is the last one thrown by
303-
the `RetryCallback`.
304-
305-
Note that, when there is more than one listener, they are in a list, so there is an order.
306-
In this case, `open` is called in the same order while `onError` and `close` are called in
307-
reverse order.
308-
309-
[[declarativeRetry]]
310-
=== Declarative Retry
311-
312-
Sometimes, there is some business processing that you know you want to retry every time it
313-
happens. The classic example of this is the remote service call. Spring Batch provides an
314-
AOP interceptor that wraps a method call in a `RetryOperations` implementation for just
315-
this purpose. The `RetryOperationsInterceptor` executes the intercepted method and retries
316-
on failure according to the `RetryPolicy` in the provided `RepeatTemplate`.
317-
318-
[role="xmlContent"]
319-
The following example shows a declarative retry that uses the Spring AOP namespace to
320-
retry a service call to a method called `remoteCall` (for more detail on how to configure
321-
AOP interceptors, see the Spring User Guide):
322-
323-
[source, xml, role="xmlContent"]
324-
----
325-
<aop:config>
326-
<aop:pointcut id="transactional"
327-
expression="execution(* com..*Service.remoteCall(..))" />
328-
<aop:advisor pointcut-ref="transactional"
329-
advice-ref="retryAdvice" order="-1"/>
330-
</aop:config>
331-
332-
<bean id="retryAdvice"
333-
class="org.springframework.retry.interceptor.RetryOperationsInterceptor"/>
334-
----
335-
336-
[role="javaContent"]
337-
The following example shows a declarative retry that uses java configuration to retry a
338-
service call to a method called `remoteCall` (for more detail on how to configure AOP
339-
interceptors, see the Spring User Guide):
340-
341-
[source, java, role="javaContent"]
342-
----
343-
@Bean
344-
public MyService myService() {
345-
ProxyFactory factory = new ProxyFactory(RepeatOperations.class.getClassLoader());
346-
factory.setInterfaces(MyService.class);
347-
factory.setTarget(new MyService());
348-
349-
MyService service = (MyService) factory.getProxy();
350-
JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
351-
pointcut.setPatterns(".*remoteCall.*");
352-
353-
RetryOperationsInterceptor interceptor = new RetryOperationsInterceptor();
354-
355-
((Advised) service).addAdvisor(new DefaultPointcutAdvisor(pointcut, interceptor));
356-
357-
return service;
358-
}
359-
----
360-
361-
The preceding example uses a default `RetryTemplate` inside the interceptor. To change the
362-
policies or listeners, you can inject an instance of `RetryTemplate` into the interceptor.

0 commit comments

Comments
 (0)