diff --git a/spring-core/src/main/java/org/springframework/core/retry/RetryCallback.java b/spring-core/src/main/java/org/springframework/core/retry/RetryCallback.java index b538f17330f..05463440a19 100644 --- a/spring-core/src/main/java/org/springframework/core/retry/RetryCallback.java +++ b/spring-core/src/main/java/org/springframework/core/retry/RetryCallback.java @@ -17,7 +17,9 @@ package org.springframework.core.retry; /** - * Callback interface for a retryable piece of code. Used in conjunction with {@link RetryOperations}. + * Callback interface for a retryable block of code. + * + *

Used in conjunction with {@link RetryOperations}. * * @author Mahmoud Ben Hassine * @since 7.0 @@ -35,11 +37,13 @@ public interface RetryCallback { R run() throws Throwable; /** - * A unique logical name for this callback to distinguish retries around - * business operations. - * @return the name of the callback. Defaults to the class name. + * A unique, logical name for this callback, used to distinguish retries for + * different business operations. + *

Defaults to the fully-qualified class name. + * @return the name of the callback */ default String getName() { return getClass().getName(); } + } diff --git a/spring-core/src/main/java/org/springframework/core/retry/RetryException.java b/spring-core/src/main/java/org/springframework/core/retry/RetryException.java index facbf6b24a6..93c46c19f36 100644 --- a/spring-core/src/main/java/org/springframework/core/retry/RetryException.java +++ b/spring-core/src/main/java/org/springframework/core/retry/RetryException.java @@ -19,7 +19,7 @@ package org.springframework.core.retry; import java.io.Serial; /** - * Exception class for exhausted retries. + * Exception thrown when a {@link RetryPolicy} has been exhausted. * * @author Mahmoud Ben Hassine * @since 7.0 @@ -30,18 +30,19 @@ public class RetryException extends Exception { @Serial private static final long serialVersionUID = 5439915454935047936L; + /** - * Create a new exception with a message. - * @param message the exception's message + * Create a new {@code RetryException} for the supplied message. + * @param message the detail message */ public RetryException(String message) { super(message); } /** - * Create a new exception with a message and a cause. - * @param message the exception's message - * @param cause the exception's cause + * Create a new {@code RetryException} for the supplied message and cause. + * @param message the detail message + * @param cause the root cause */ public RetryException(String message, Throwable cause) { super(message, cause); diff --git a/spring-core/src/main/java/org/springframework/core/retry/RetryExecution.java b/spring-core/src/main/java/org/springframework/core/retry/RetryExecution.java index 13a10e272bb..f021911cd7d 100644 --- a/spring-core/src/main/java/org/springframework/core/retry/RetryExecution.java +++ b/spring-core/src/main/java/org/springframework/core/retry/RetryExecution.java @@ -17,7 +17,8 @@ package org.springframework.core.retry; /** - * Strategy interface to define a retry execution. + * Strategy interface to define a retry execution created for a given + * {@link RetryPolicy}. * *

Implementations may be stateful but do not need to be thread-safe. * diff --git a/spring-core/src/main/java/org/springframework/core/retry/RetryListener.java b/spring-core/src/main/java/org/springframework/core/retry/RetryListener.java index eb766b13ac3..9586e778fd6 100644 --- a/spring-core/src/main/java/org/springframework/core/retry/RetryListener.java +++ b/spring-core/src/main/java/org/springframework/core/retry/RetryListener.java @@ -48,15 +48,15 @@ public interface RetryListener { /** * Called every time a retry attempt fails. * @param retryExecution the retry execution - * @param throwable the throwable thrown by the callback + * @param throwable the exception thrown by the callback */ default void onRetryFailure(RetryExecution retryExecution, Throwable throwable) { } /** - * Called once the retry policy is exhausted. + * Called if the {@link RetryPolicy} is exhausted. * @param retryExecution the retry execution - * @param throwable the last throwable thrown by the callback + * @param throwable the last exception thrown by the {@link RetryCallback} */ default void onRetryPolicyExhaustion(RetryExecution retryExecution, Throwable throwable) { } diff --git a/spring-core/src/main/java/org/springframework/core/retry/RetryOperations.java b/spring-core/src/main/java/org/springframework/core/retry/RetryOperations.java index cf3744b74c0..a731b8a3571 100644 --- a/spring-core/src/main/java/org/springframework/core/retry/RetryOperations.java +++ b/spring-core/src/main/java/org/springframework/core/retry/RetryOperations.java @@ -19,7 +19,7 @@ package org.springframework.core.retry; import org.jspecify.annotations.Nullable; /** - * Main entry point to the core retry functionality. Defines a set of retryable operations. + * Interface specifying basic retry operations. * *

Implemented by {@link RetryTemplate}. Not often used directly, but a useful * option to enhance testability, as it can easily be mocked or stubbed. @@ -31,15 +31,16 @@ import org.jspecify.annotations.Nullable; public interface RetryOperations { /** - * Retry the given callback (according to the retry policy configured at the implementation level) - * until it succeeds or eventually throw an exception if the retry policy is exhausted. + * Execute the given callback (according to the {@link RetryPolicy} configured + * at the implementation level) until it succeeds, or eventually throw an + * exception if the {@code RetryPolicy} is exhausted. * @param retryCallback the callback to call initially and retry if needed - * @param the type of the callback's result - * @return the callback's result - * @throws RetryException thrown if the retry policy is exhausted. All attempt exceptions - * should be added as suppressed exceptions to the final exception. + * @param the type of the result + * @return the result of the callback, if any + * @throws RetryException if the {@code RetryPolicy} is exhausted; exceptions + * encountered during retry attempts should be made available as suppressed + * exceptions */ R execute(RetryCallback retryCallback) throws RetryException; } - diff --git a/spring-core/src/main/java/org/springframework/core/retry/RetryPolicy.java b/spring-core/src/main/java/org/springframework/core/retry/RetryPolicy.java index 16243a37377..d7335fb9143 100644 --- a/spring-core/src/main/java/org/springframework/core/retry/RetryPolicy.java +++ b/spring-core/src/main/java/org/springframework/core/retry/RetryPolicy.java @@ -25,8 +25,8 @@ package org.springframework.core.retry; public interface RetryPolicy { /** - * Start a new retry execution. - * @return a fresh {@link RetryExecution} ready to be used + * Start a new execution for this retry policy. + * @return a new {@link RetryExecution} */ RetryExecution start(); diff --git a/spring-core/src/main/java/org/springframework/core/retry/RetryTemplate.java b/spring-core/src/main/java/org/springframework/core/retry/RetryTemplate.java index b03ebf2a72e..1285fe84801 100644 --- a/spring-core/src/main/java/org/springframework/core/retry/RetryTemplate.java +++ b/spring-core/src/main/java/org/springframework/core/retry/RetryTemplate.java @@ -31,23 +31,28 @@ import org.springframework.util.backoff.BackOffExecution; import org.springframework.util.backoff.FixedBackOff; /** - * A basic implementation of {@link RetryOperations} that uses a - * {@link RetryPolicy} and a {@link BackOff} to retry a - * {@link RetryCallback}. By default, the callback will be called - * 3 times with a fixed backoff of 1 second. + * A basic implementation of {@link RetryOperations} that invokes and potentially + * retries a {@link RetryCallback} based on a configured {@link RetryPolicy} and + * {@link BackOff} policy. * - *

It is also possible to register a {@link RetryListener} to intercept and inject code - * during key retry phases (before a retry attempt, after a retry attempt, etc.). + *

By default, a callback will be invoked at most 3 times with a fixed backoff + * of 1 second. + * + *

A {@link RetryListener} can be {@linkplain #setRetryListener(RetryListener) + * registered} to intercept and inject behavior during key retry phases (before a + * retry attempt, after a retry attempt, etc.). * *

All retry operations performed by this class are logged at debug level, - * using "org.springframework.core.retry.RetryTemplate" as log category. + * using {@code "org.springframework.core.retry.RetryTemplate"} as the log category. * * @author Mahmoud Ben Hassine + * @author Sam Brannen * @since 7.0 * @see RetryOperations * @see RetryPolicy * @see BackOff * @see RetryListener + * @see RetryCallback */ public class RetryTemplate implements RetryOperations { @@ -55,28 +60,31 @@ public class RetryTemplate implements RetryOperations { protected RetryPolicy retryPolicy = new MaxRetryAttemptsPolicy(); - protected BackOff backOffPolicy = new FixedBackOff(); + protected BackOff backOffPolicy = new FixedBackOff(1000, Long.MAX_VALUE); protected RetryListener retryListener = new RetryListener() { }; /** - * Create a new retry template with default settings. + * Create a new {@code RetryTemplate} with maximum 3 retry attempts and a + * fixed backoff of 1 second. */ public RetryTemplate() { } /** - * Create a new retry template with a custom {@link RetryPolicy}. + * Create a new {@code RetryTemplate} with a custom {@link RetryPolicy} and a + * fixed backoff of 1 second. * @param retryPolicy the retry policy to use */ public RetryTemplate(RetryPolicy retryPolicy) { - Assert.notNull(retryPolicy, "Retry policy must not be null"); + Assert.notNull(retryPolicy, "RetryPolicy must not be null"); this.retryPolicy = retryPolicy; } /** - * Create a new retry template with a custom {@link RetryPolicy} and {@link BackOff}. + * Create a new {@code RetryTemplate} with a custom {@link RetryPolicy} and + * {@link BackOff} policy. * @param retryPolicy the retry policy to use * @param backOffPolicy the backoff policy to use */ @@ -87,8 +95,10 @@ public class RetryTemplate implements RetryOperations { } /** - * Set the {@link RetryPolicy} to use. Defaults to MaxAttemptsRetryPolicy(). - * @param retryPolicy the retry policy to use. Must not be null. + * Set the {@link RetryPolicy} to use. + *

Defaults to {@code new MaxRetryAttemptsPolicy()}. + * @param retryPolicy the retry policy to use + * @see MaxRetryAttemptsPolicy */ public void setRetryPolicy(RetryPolicy retryPolicy) { Assert.notNull(retryPolicy, "Retry policy must not be null"); @@ -96,8 +106,10 @@ public class RetryTemplate implements RetryOperations { } /** - * Set the {@link BackOff} to use. Defaults to FixedBackOffPolicy(Duration.ofSeconds(1)). - * @param backOffPolicy the backoff policy to use. Must not be null. + * Set the {@link BackOff} policy to use. + *

Defaults to {@code new FixedBackOff(1000, Long.MAX_VALUE))}. + * @param backOffPolicy the backoff policy to use + * @see FixedBackOff */ public void setBackOffPolicy(BackOff backOffPolicy) { Assert.notNull(backOffPolicy, "BackOff policy must not be null"); @@ -105,9 +117,10 @@ public class RetryTemplate implements RetryOperations { } /** - * Set the {@link RetryListener} to use. Defaults to a NoOp implementation. - * If multiple listeners are needed, use a {@link CompositeRetryListener}. - * @param retryListener the retry listener to use. Must not be null. + * Set the {@link RetryListener} to use. + *

If multiple listeners are needed, use a {@link CompositeRetryListener}. + *

Defaults to a no-op implementation. + * @param retryListener the retry listener to use */ public void setRetryListener(RetryListener retryListener) { Assert.notNull(retryListener, "Retry listener must not be null"); @@ -115,60 +128,65 @@ public class RetryTemplate implements RetryOperations { } /** - * Call the retry callback according to the configured retry and backoff policies. - * If the callback succeeds, its result is returned. Otherwise, a {@link RetryException} - * will be thrown to the caller having all attempt exceptions as suppressed exceptions. + * Execute the supplied {@link RetryCallback} according to the configured + * retry and backoff policies. + *

If the callback succeeds, its result will be returned. Otherwise, a + * {@link RetryException} will be thrown to the caller. * @param retryCallback the callback to call initially and retry if needed * @param the type of the result - * @return the result of the callback if any - * @throws RetryException thrown if the retry policy is exhausted. All attempt exceptions - * are added as suppressed exceptions to the final exception. + * @return the result of the callback, if any + * @throws RetryException if the {@code RetryPolicy} is exhausted; exceptions + * encountered during retry attempts are available as suppressed exceptions */ @Override public R execute(RetryCallback retryCallback) throws RetryException { - Assert.notNull(retryCallback, "Retry Callback must not be null"); String callbackName = retryCallback.getName(); - // initial attempt + // Initial attempt try { - logger.debug(() -> "About to execute callback '" + callbackName + "'"); + logger.debug(() -> "Preparing to execute callback '" + callbackName + "'"); R result = retryCallback.run(); - logger.debug(() -> "Callback '" + callbackName + "' executed successfully"); + logger.debug(() -> "Callback '" + callbackName + "' completed successfully"); return result; } catch (Throwable initialException) { - logger.debug(initialException, () -> "Execution of callback '" + callbackName + "' failed, initiating the retry process"); - // retry process starts here + logger.debug(initialException, + () -> "Execution of callback '" + callbackName + "' failed; initiating the retry process"); + // Retry process starts here RetryExecution retryExecution = this.retryPolicy.start(); BackOffExecution backOffExecution = this.backOffPolicy.start(); List suppressedExceptions = new ArrayList<>(); Throwable retryException = initialException; while (retryExecution.shouldRetry(retryException)) { - logger.debug(() -> "About to retry callback '" + callbackName + "'"); + logger.debug(() -> "Preparing to retry callback '" + callbackName + "'"); try { this.retryListener.beforeRetry(retryExecution); R result = retryCallback.run(); this.retryListener.onRetrySuccess(retryExecution, result); - logger.debug(() -> "Callback '" + callbackName + "' retried successfully"); + logger.debug(() -> "Callback '" + callbackName + "' completed successfully after retry"); return result; } catch (Throwable currentAttemptException) { this.retryListener.onRetryFailure(retryExecution, currentAttemptException); try { long duration = backOffExecution.nextBackOff(); - logger.debug(() -> "Retry callback '" + callbackName + "' failed for " + currentAttemptException.getMessage() + ", backing off for " + duration + "ms"); + logger.debug(() -> "Retry callback '" + callbackName + "' failed due to '" + + currentAttemptException.getMessage() + "'; backing off for " + duration + "ms"); Thread.sleep(duration); } catch (InterruptedException interruptedException) { Thread.currentThread().interrupt(); - throw new RetryException("Unable to backoff for retry callback '" + callbackName + "'", interruptedException); + throw new RetryException("Unable to back off for retry callback '" + callbackName + "'", + interruptedException); } suppressedExceptions.add(currentAttemptException); retryException = currentAttemptException; } } - // retry policy exhausted at this point, throwing a RetryException with the initial exception as cause and remaining attempts exceptions as suppressed - RetryException finalException = new RetryException("Retry policy for callback '" + callbackName + "' exhausted, aborting execution", initialException); + // The RetryPolicy has exhausted at this point, so we throw a RetryException with the + // initial exception as the cause and remaining exceptions as suppressed exceptions. + RetryException finalException = new RetryException("Retry policy for callback '" + callbackName + + "' exhausted; aborting execution", initialException); suppressedExceptions.forEach(finalException::addSuppressed); this.retryListener.onRetryPolicyExhaustion(retryExecution, finalException); throw finalException; diff --git a/spring-core/src/main/java/org/springframework/core/retry/support/CompositeRetryListener.java b/spring-core/src/main/java/org/springframework/core/retry/support/CompositeRetryListener.java index 9fa958d7de6..f97a04125b9 100644 --- a/spring-core/src/main/java/org/springframework/core/retry/support/CompositeRetryListener.java +++ b/spring-core/src/main/java/org/springframework/core/retry/support/CompositeRetryListener.java @@ -25,8 +25,9 @@ import org.springframework.core.retry.RetryTemplate; import org.springframework.util.Assert; /** - * A composite implementation of the {@link RetryListener} interface. This class - * is used to compose multiple listeners within a {@link RetryTemplate}. + * A composite implementation of the {@link RetryListener} interface. + * + *

This class is used to compose multiple listeners within a {@link RetryTemplate}. * *

Delegate listeners will be called in their registration order. * @@ -35,30 +36,30 @@ import org.springframework.util.Assert; */ public class CompositeRetryListener implements RetryListener { - private final List listeners; + private final List listeners = new LinkedList<>(); + /** - * Create a new {@link CompositeRetryListener}. + * Create a new {@code CompositeRetryListener}. */ public CompositeRetryListener() { - this.listeners = new LinkedList<>(); } /** - * Create a new {@link CompositeRetryListener} with a list of delegates. - * @param listeners the delegate listeners to register. Must not be empty. + * Create a new {@code CompositeRetryListener} with the supplied list of + * delegates. + * @param listeners the list of delegate listeners to register; must not be empty */ public CompositeRetryListener(List listeners) { Assert.notEmpty(listeners, "RetryListener List must not be empty"); - this.listeners = List.copyOf(listeners); + this.listeners.addAll(listeners); } /** * Add a new listener to the list of delegates. - * @param listener the listener to add. Must not be null. + * @param listener the listener to add */ public void addListener(RetryListener listener) { - Assert.notNull(listener, "Retry listener must not be null"); this.listeners.add(listener); } diff --git a/spring-core/src/main/java/org/springframework/core/retry/support/MaxRetryAttemptsPolicy.java b/spring-core/src/main/java/org/springframework/core/retry/support/MaxRetryAttemptsPolicy.java index 4d890f7c74e..1e82bf1c66f 100644 --- a/spring-core/src/main/java/org/springframework/core/retry/support/MaxRetryAttemptsPolicy.java +++ b/spring-core/src/main/java/org/springframework/core/retry/support/MaxRetryAttemptsPolicy.java @@ -21,7 +21,8 @@ import org.springframework.core.retry.RetryPolicy; import org.springframework.util.Assert; /** - * A {@link RetryPolicy} based on a number of attempts that should not exceed a maximum number. + * A {@link RetryPolicy} based on a number of attempts that should not exceed a + * configured maximum number. * * @author Mahmoud Ben Hassine * @since 7.0 @@ -29,30 +30,32 @@ import org.springframework.util.Assert; public class MaxRetryAttemptsPolicy implements RetryPolicy { /** - * The default maximum number of retry attempts. + * The default maximum number of retry attempts: {@value}. */ public static final int DEFAULT_MAX_RETRY_ATTEMPTS = 3; + private int maxRetryAttempts = DEFAULT_MAX_RETRY_ATTEMPTS; + /** - * Create a new {@link MaxRetryAttemptsPolicy} with the default maximum number of retry attempts. + * Create a new {@code MaxRetryAttemptsPolicy} with the default maximum number + * of retry attempts. + * @see #DEFAULT_MAX_RETRY_ATTEMPTS */ public MaxRetryAttemptsPolicy() { } /** - * Create a new {@link MaxRetryAttemptsPolicy} with the specified maximum number of retry attempts. - * @param maxRetryAttempts the maximum number of retry attempts. Must be greater than zero. + * Create a new {@code MaxRetryAttemptsPolicy} with the specified maximum number + * of retry attempts. + * @param maxRetryAttempts the maximum number of retry attempts; must be greater + * than zero */ public MaxRetryAttemptsPolicy(int maxRetryAttempts) { setMaxRetryAttempts(maxRetryAttempts); } - /** - * Start a new retry execution. - * @return a fresh {@link MaxRetryAttemptsPolicyExecution} ready to be used - */ @Override public RetryExecution start() { return new MaxRetryAttemptsPolicyExecution(); @@ -60,13 +63,15 @@ public class MaxRetryAttemptsPolicy implements RetryPolicy { /** * Set the maximum number of retry attempts. - * @param maxRetryAttempts the maximum number of retry attempts. Must be greater than zero. + * @param maxRetryAttempts the maximum number of retry attempts; must be greater + * than zero */ public void setMaxRetryAttempts(int maxRetryAttempts) { Assert.isTrue(maxRetryAttempts > 0, "Max retry attempts must be greater than zero"); this.maxRetryAttempts = maxRetryAttempts; } + /** * A {@link RetryExecution} based on a maximum number of retry attempts. */ @@ -76,9 +81,8 @@ public class MaxRetryAttemptsPolicy implements RetryPolicy { @Override public boolean shouldRetry(Throwable throwable) { - return this.retryAttempts++ < MaxRetryAttemptsPolicy.this.maxRetryAttempts; + return (this.retryAttempts++ < MaxRetryAttemptsPolicy.this.maxRetryAttempts); } - } } diff --git a/spring-core/src/main/java/org/springframework/core/retry/support/MaxRetryDurationPolicy.java b/spring-core/src/main/java/org/springframework/core/retry/support/MaxRetryDurationPolicy.java index 0b69f6cf820..bbd32d4bb5d 100644 --- a/spring-core/src/main/java/org/springframework/core/retry/support/MaxRetryDurationPolicy.java +++ b/spring-core/src/main/java/org/springframework/core/retry/support/MaxRetryDurationPolicy.java @@ -24,7 +24,7 @@ import org.springframework.core.retry.RetryPolicy; import org.springframework.util.Assert; /** - * A {@link RetryPolicy} based on a timeout. + * A {@link RetryPolicy} based on a maximum retry duration. * * @author Mahmoud Ben Hassine * @since 7.0 @@ -32,30 +32,31 @@ import org.springframework.util.Assert; public class MaxRetryDurationPolicy implements RetryPolicy { /** - * The default maximum retry duration. + * The default maximum retry duration: 3 seconds. */ public static final Duration DEFAULT_MAX_RETRY_DURATION = Duration.ofSeconds(3); + private Duration maxRetryDuration = DEFAULT_MAX_RETRY_DURATION; + /** - * Create a new {@link MaxRetryDurationPolicy} with the default maximum retry duration. + * Create a new {@code MaxRetryDurationPolicy} with the default maximum retry + * duration. + * @see #DEFAULT_MAX_RETRY_DURATION */ public MaxRetryDurationPolicy() { } /** - * Create a new {@link MaxRetryDurationPolicy} with the specified maximum retry duration. - * @param maxRetryDuration the maximum retry duration. Must be positive. + * Create a new {@code MaxRetryDurationPolicy} with the specified maximum retry + * duration. + * @param maxRetryDuration the maximum retry duration; must be positive */ public MaxRetryDurationPolicy(Duration maxRetryDuration) { setMaxRetryDuration(maxRetryDuration); } - /** - * Start a new retry execution. - * @return a fresh {@link MaxRetryDurationPolicyExecution} ready to be used - */ @Override public RetryExecution start() { return new MaxRetryDurationPolicyExecution(); @@ -63,7 +64,7 @@ public class MaxRetryDurationPolicy implements RetryPolicy { /** * Set the maximum retry duration. - * @param maxRetryDuration the maximum retry duration. Must be positive. + * @param maxRetryDuration the maximum retry duration; must be positive */ public void setMaxRetryDuration(Duration maxRetryDuration) { Assert.isTrue(!maxRetryDuration.isNegative() && !maxRetryDuration.isZero(), @@ -83,6 +84,6 @@ public class MaxRetryDurationPolicy implements RetryPolicy { Duration currentRetryDuration = Duration.between(this.retryStartTime, LocalDateTime.now()); return currentRetryDuration.compareTo(MaxRetryDurationPolicy.this.maxRetryDuration) <= 0; } - } + } diff --git a/spring-core/src/main/java/org/springframework/core/retry/support/PredicateRetryPolicy.java b/spring-core/src/main/java/org/springframework/core/retry/support/PredicateRetryPolicy.java index 100826d025d..afe186a5e9e 100644 --- a/spring-core/src/main/java/org/springframework/core/retry/support/PredicateRetryPolicy.java +++ b/spring-core/src/main/java/org/springframework/core/retry/support/PredicateRetryPolicy.java @@ -22,7 +22,7 @@ import org.springframework.core.retry.RetryExecution; import org.springframework.core.retry.RetryPolicy; /** - * A {@link RetryPolicy} based on a predicate. + * A {@link RetryPolicy} based on a {@link Predicate}. * * @author Mahmoud Ben Hassine * @since 7.0 @@ -31,18 +31,18 @@ public class PredicateRetryPolicy implements RetryPolicy { private final Predicate predicate; + /** - * Create a new {@link PredicateRetryPolicy} with the given predicate. - * @param predicate the predicate to use for determining whether to retry the exception or not + * Create a new {@code PredicateRetryPolicy} with the given predicate. + * @param predicate the predicate to use for determining whether to retry an + * operation based on a given {@link Throwable} */ public PredicateRetryPolicy(Predicate predicate) { this.predicate = predicate; } - /** - * Start a new retry execution. - * @return a fresh {@link RetryExecution} ready to be used - */ + + @Override public RetryExecution start() { return this.predicate::test; } diff --git a/spring-core/src/test/java/org/springframework/core/retry/RetryTemplateTests.java b/spring-core/src/test/java/org/springframework/core/retry/RetryTemplateTests.java index a08c2ea8a6b..1e44f37d781 100644 --- a/spring-core/src/test/java/org/springframework/core/retry/RetryTemplateTests.java +++ b/spring-core/src/test/java/org/springframework/core/retry/RetryTemplateTests.java @@ -16,27 +16,31 @@ package org.springframework.core.retry; -import org.assertj.core.api.ThrowableAssert.ThrowingCallable; import org.junit.jupiter.api.Test; import org.springframework.core.retry.support.MaxRetryAttemptsPolicy; import org.springframework.util.backoff.FixedBackOff; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** * Tests for {@link RetryTemplate}. * * @author Mahmoud Ben Hassine + * @author Sam Brannen + * @since 7.0 */ class RetryTemplateTests { + private final RetryTemplate retryTemplate = new RetryTemplate(); + @Test - void testRetryWithSuccess() throws Exception { - // given + void retryWithSuccess() throws Exception { RetryCallback retryCallback = new RetryCallback<>() { + int failure; + @Override public String run() throws Exception { if (failure++ < 2) { @@ -50,21 +54,16 @@ class RetryTemplateTests { return "greeting service"; } }; - RetryTemplate retryTemplate = new RetryTemplate(); - retryTemplate.setRetryPolicy(new MaxRetryAttemptsPolicy()); - retryTemplate.setBackOffPolicy(new FixedBackOff()); - // when - String result = retryTemplate.execute(retryCallback); + retryTemplate.setBackOffPolicy(new FixedBackOff(100, Long.MAX_VALUE)); - // then - assertThat(result).isEqualTo("hello world"); + assertThat(retryTemplate.execute(retryCallback)).isEqualTo("hello world"); } @Test - void testRetryWithFailure() { - // given + void retryWithFailure() { Exception exception = new Exception("Error while invoking greeting service"); + RetryCallback retryCallback = new RetryCallback<>() { @Override public String run() throws Exception { @@ -76,31 +75,27 @@ class RetryTemplateTests { return "greeting service"; } }; - RetryTemplate retryTemplate = new RetryTemplate(); - retryTemplate.setRetryPolicy(new MaxRetryAttemptsPolicy()); - retryTemplate.setBackOffPolicy(new FixedBackOff()); - - // when - ThrowingCallable throwingCallable = () -> retryTemplate.execute(retryCallback); - - // then - assertThatThrownBy(throwingCallable) - .isInstanceOf(RetryException.class) - .hasMessage("Retry policy for callback 'greeting service' exhausted, aborting execution") - .hasCause(exception); + + retryTemplate.setBackOffPolicy(new FixedBackOff(100, Long.MAX_VALUE)); + + assertThatExceptionOfType(RetryException.class) + .isThrownBy(() -> retryTemplate.execute(retryCallback)) + .withMessage("Retry policy for callback 'greeting service' exhausted; aborting execution") + .withCause(exception); } @Test - void testRetrySpecificException() { - // given + void retrySpecificException() { + + @SuppressWarnings("serial") class TechnicalException extends Exception { - @java.io.Serial - private static final long serialVersionUID = 1L; public TechnicalException(String message) { super(message); } } - final TechnicalException technicalException = new TechnicalException("Error while invoking greeting service"); + + TechnicalException technicalException = new TechnicalException("Error while invoking greeting service"); + RetryCallback retryCallback = new RetryCallback<>() { @Override public String run() throws TechnicalException { @@ -112,11 +107,14 @@ class RetryTemplateTests { return "greeting service"; } }; + MaxRetryAttemptsPolicy retryPolicy = new MaxRetryAttemptsPolicy() { @Override public RetryExecution start() { return new RetryExecution() { + int retryAttempts; + @Override public boolean shouldRetry(Throwable throwable) { return this.retryAttempts++ < 3 && throwable instanceof TechnicalException; @@ -124,18 +122,13 @@ class RetryTemplateTests { }; } }; - RetryTemplate retryTemplate = new RetryTemplate(); retryTemplate.setRetryPolicy(retryPolicy); - retryTemplate.setBackOffPolicy(new FixedBackOff()); - - // when - ThrowingCallable throwingCallable = () -> retryTemplate.execute(retryCallback); + retryTemplate.setBackOffPolicy(new FixedBackOff(100, Long.MAX_VALUE)); - // then - assertThatThrownBy(throwingCallable) - .isInstanceOf(RetryException.class) - .hasMessage("Retry policy for callback 'greeting service' exhausted, aborting execution") - .hasCause(technicalException); + assertThatExceptionOfType(RetryException.class) + .isThrownBy(() -> retryTemplate.execute(retryCallback)) + .withMessage("Retry policy for callback 'greeting service' exhausted; aborting execution") + .withCause(technicalException); } } diff --git a/spring-core/src/test/java/org/springframework/core/retry/support/ComposedRetryListenerTests.java b/spring-core/src/test/java/org/springframework/core/retry/support/ComposedRetryListenerTests.java index 440b08644a5..7ef1247c993 100644 --- a/spring-core/src/test/java/org/springframework/core/retry/support/ComposedRetryListenerTests.java +++ b/spring-core/src/test/java/org/springframework/core/retry/support/ComposedRetryListenerTests.java @@ -76,4 +76,5 @@ class ComposedRetryListenerTests { verify(this.listener1).onRetryPolicyExhaustion(retryExecution, exception); verify(this.listener2).onRetryPolicyExhaustion(retryExecution, exception); } + } diff --git a/spring-core/src/test/java/org/springframework/core/retry/support/MaxRetryAttemptsPolicyTests.java b/spring-core/src/test/java/org/springframework/core/retry/support/MaxRetryAttemptsPolicyTests.java index 02b7599f0f0..2912f5b9a12 100644 --- a/spring-core/src/test/java/org/springframework/core/retry/support/MaxRetryAttemptsPolicyTests.java +++ b/spring-core/src/test/java/org/springframework/core/retry/support/MaxRetryAttemptsPolicyTests.java @@ -21,7 +21,7 @@ import org.junit.jupiter.api.Test; import org.springframework.core.retry.RetryExecution; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.Mockito.mock; /** @@ -32,7 +32,7 @@ import static org.mockito.Mockito.mock; class MaxRetryAttemptsPolicyTests { @Test - void testDefaultMaxRetryAttempts() { + void defaultMaxRetryAttempts() { // given MaxRetryAttemptsPolicy retryPolicy = new MaxRetryAttemptsPolicy(); Throwable throwable = mock(); @@ -48,8 +48,10 @@ class MaxRetryAttemptsPolicyTests { } @Test - void testInvalidMaxRetryAttempts() { - assertThatThrownBy(() -> new MaxRetryAttemptsPolicy(-1)) - .hasMessage("Max retry attempts must be greater than zero"); + void invalidMaxRetryAttempts() { + assertThatIllegalArgumentException() + .isThrownBy(() -> new MaxRetryAttemptsPolicy(-1)) + .withMessage("Max retry attempts must be greater than zero"); } + } diff --git a/spring-core/src/test/java/org/springframework/core/retry/support/MaxRetryDurationPolicyTests.java b/spring-core/src/test/java/org/springframework/core/retry/support/MaxRetryDurationPolicyTests.java index bd3a0555cde..3f8a82896e5 100644 --- a/spring-core/src/test/java/org/springframework/core/retry/support/MaxRetryDurationPolicyTests.java +++ b/spring-core/src/test/java/org/springframework/core/retry/support/MaxRetryDurationPolicyTests.java @@ -20,7 +20,7 @@ import java.time.Duration; import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * Tests for {@link MaxRetryDurationPolicy}. @@ -30,8 +30,10 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; class MaxRetryDurationPolicyTests { @Test - void testInvalidMaxRetryDuration() { - assertThatThrownBy(() -> new MaxRetryDurationPolicy(Duration.ZERO)) - .hasMessage("Max retry duration must be positive"); + void invalidMaxRetryDuration() { + assertThatIllegalArgumentException() + .isThrownBy(() -> new MaxRetryDurationPolicy(Duration.ZERO)) + .withMessage("Max retry duration must be positive"); } + } diff --git a/spring-core/src/test/java/org/springframework/core/retry/support/PredicateRetryPolicyTests.java b/spring-core/src/test/java/org/springframework/core/retry/support/PredicateRetryPolicyTests.java index 50c54c7f04a..2ddac2f8588 100644 --- a/spring-core/src/test/java/org/springframework/core/retry/support/PredicateRetryPolicyTests.java +++ b/spring-core/src/test/java/org/springframework/core/retry/support/PredicateRetryPolicyTests.java @@ -32,13 +32,13 @@ import static org.assertj.core.api.Assertions.assertThat; class PredicateRetryPolicyTests { @Test - void testPredicateRetryPolicy() { + void predicateRetryPolicy() { // given class MyException extends Exception { @java.io.Serial private static final long serialVersionUID = 1L; } - Predicate predicate = throwable -> throwable instanceof MyException; + Predicate predicate = MyException.class::isInstance; PredicateRetryPolicy retryPolicy = new PredicateRetryPolicy(predicate); // when