1717package org .springframework .core .retry ;
1818
1919import java .time .Duration ;
20- import java .util .ArrayList ;
21- import java .util .List ;
20+ import java .util .ArrayDeque ;
21+ import java .util .Deque ;
22+ import java .util .Iterator ;
2223
23- import org .apache .commons .logging .LogFactory ;
2424import org .jspecify .annotations .Nullable ;
2525
2626import org .springframework .core .log .LogAccessor ;
27- import org .springframework .core .retry .support .CompositeRetryListener ;
2827import org .springframework .core .retry .support .MaxRetryAttemptsPolicy ;
2928import org .springframework .util .Assert ;
3029import org .springframework .util .backoff .BackOff ;
4847 *
4948 * @author Mahmoud Ben Hassine
5049 * @author Sam Brannen
50+ * @author Juergen Hoeller
5151 * @since 7.0
5252 * @see RetryOperations
5353 * @see RetryPolicy
5757 */
5858public class RetryTemplate implements RetryOperations {
5959
60- protected final LogAccessor logger = new LogAccessor (LogFactory . getLog ( getClass ()) );
60+ private static final LogAccessor logger = new LogAccessor (RetryTemplate . class );
6161
62- protected RetryPolicy retryPolicy = new MaxRetryAttemptsPolicy ();
62+ private RetryPolicy retryPolicy = new MaxRetryAttemptsPolicy ();
6363
64- protected BackOff backOffPolicy = new FixedBackOff (Duration .ofSeconds (1 ));
64+ private BackOff backOffPolicy = new FixedBackOff (Duration .ofSeconds (1 ));
6565
66- protected RetryListener retryListener = new RetryListener () {
67- };
66+ private RetryListener retryListener = new RetryListener () {};
6867
6968
7069 /**
@@ -121,7 +120,8 @@ public void setBackOffPolicy(BackOff backOffPolicy) {
121120
122121 /**
123122 * Set the {@link RetryListener} to use.
124- * <p>If multiple listeners are needed, use a {@link CompositeRetryListener}.
123+ * <p>If multiple listeners are needed, use a
124+ * {@link org.springframework.core.retry.support.CompositeRetryListener}.
125125 * <p>Defaults to a <em>no-op</em> implementation.
126126 * @param retryListener the retry listener to use
127127 */
@@ -158,10 +158,26 @@ public void setRetryListener(RetryListener retryListener) {
158158 // Retry process starts here
159159 RetryExecution retryExecution = this .retryPolicy .start ();
160160 BackOffExecution backOffExecution = this .backOffPolicy .start ();
161- List <Throwable > suppressedExceptions = new ArrayList <>();
161+ Deque <Throwable > exceptions = new ArrayDeque <>();
162+ exceptions .add (initialException );
162163
163164 Throwable retryException = initialException ;
164165 while (retryExecution .shouldRetry (retryException )) {
166+ try {
167+ long duration = backOffExecution .nextBackOff ();
168+ if (duration == BackOffExecution .STOP ) {
169+ break ;
170+ }
171+ logger .debug (() -> "Backing off for %dms after retryable operation '%s'"
172+ .formatted (duration , retryableName ));
173+ Thread .sleep (duration );
174+ }
175+ catch (InterruptedException interruptedException ) {
176+ Thread .currentThread ().interrupt ();
177+ throw new RetryException (
178+ "Unable to back off for retryable operation '%s'" .formatted (retryableName ),
179+ interruptedException );
180+ }
165181 logger .debug (() -> "Preparing to retry operation '%s'" .formatted (retryableName ));
166182 try {
167183 this .retryListener .beforeRetry (retryExecution );
@@ -172,29 +188,22 @@ public void setRetryListener(RetryListener retryListener) {
172188 return result ;
173189 }
174190 catch (Throwable currentAttemptException ) {
191+ logger .debug (() -> "Retry attempt for operation '%s' failed due to '%s'"
192+ .formatted (retryableName , currentAttemptException ));
175193 this .retryListener .onRetryFailure (retryExecution , currentAttemptException );
176- try {
177- long duration = backOffExecution .nextBackOff ();
178- logger .debug (() -> "Retryable operation '%s' failed due to '%s'; backing off for %dms"
179- .formatted (retryableName , currentAttemptException .getMessage (), duration ));
180- Thread .sleep (duration );
181- }
182- catch (InterruptedException interruptedException ) {
183- Thread .currentThread ().interrupt ();
184- throw new RetryException (
185- "Unable to back off for retryable operation '%s'" .formatted (retryableName ),
186- interruptedException );
187- }
188- suppressedExceptions .add (currentAttemptException );
194+ exceptions .add (currentAttemptException );
189195 retryException = currentAttemptException ;
190196 }
191197 }
198+
192199 // The RetryPolicy has exhausted at this point, so we throw a RetryException with the
193200 // initial exception as the cause and remaining exceptions as suppressed exceptions.
194201 RetryException finalException = new RetryException (
195202 "Retry policy for operation '%s' exhausted; aborting execution" .formatted (retryableName ),
196- initialException );
197- suppressedExceptions .forEach (finalException ::addSuppressed );
203+ exceptions .removeLast ());
204+ for (Iterator <Throwable > it = exceptions .descendingIterator (); it .hasNext ();) {
205+ finalException .addSuppressed (it .next ());
206+ }
198207 this .retryListener .onRetryPolicyExhaustion (retryExecution , finalException );
199208 throw finalException ;
200209 }
0 commit comments