diff --git a/src/main/java/org/springframework/retry/support/RetryTemplateBuilder.java b/src/main/java/org/springframework/retry/support/RetryTemplateBuilder.java index 367e9508..9d198c3b 100644 --- a/src/main/java/org/springframework/retry/support/RetryTemplateBuilder.java +++ b/src/main/java/org/springframework/retry/support/RetryTemplateBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2022 the original author or authors. + * Copyright 2006-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,11 +37,21 @@ import org.springframework.util.Assert; /** - * Fluent API to configure new instance of RetryTemplate. For detailed description of each - * builder method - see it's doc. + * Builder that provides a fluent API to configure new instances of {@link RetryTemplate}. * *
- * Examples:
{@code + * By default, the builder configures a {@link BinaryExceptionClassifier} that acts upon + * {@link Exception} and its subclasses without traversing causes, a + * {@link NoBackOffPolicy} and a {@link MaxAttemptsRetryPolicy} that attempts actions + * {@link MaxAttemptsRetryPolicy#DEFAULT_MAX_ATTEMPTS} times. + * + *+ * The builder is not thread-safe. + * + *
+ * Example usage: + * + *
{@code * RetryTemplate.builder() * .maxAttempts(10) * .exponentialBackoff(100, 2, 10000) @@ -61,26 +71,6 @@ * .build(); * }* - *- * The builder provides the following defaults: - *
- * The builder supports only widely used properties of {@link RetryTemplate}. More - * specific properties can be configured directly (after building). - * - *
- * Not thread safe. Building should be performed in a single thread. Also, there is no - * guarantee that all constructors of all fields are thread safe in-depth (means employing - * only volatile and final writes), so, in concurrent environment, it is recommended to - * ensure presence of happens-before between publication and any usage. (e.g. publication - * via volatile write, or other safe publication technique) - * * @author Aleksandr Shamukov * @author Artem Bilan * @author Kim In Hoi @@ -100,13 +90,12 @@ public class RetryTemplateBuilder { /* ---------------- Configure retry policy -------------- */ /** - * Limits maximum number of attempts to provided value. - *
- * Invocation of this method does not discard default exception classification rule, - * that is "retry only on {@link Exception} and it's subclasses". - * @param maxAttempts includes initial attempt and all retries. E.g: maxAttempts = 3 - * means one initial attempt and two retries. + * Attempt an action no more than {@code maxAttempts} times. + * @param maxAttempts how many times an action should be attempted. A value of 3 would + * result in an initial attempt and two retries. * @return this + * @throws IllegalArgumentException if {@code maxAttempts} is 0 or less, or if another + * retry policy has already been selected. * @see MaxAttemptsRetryPolicy */ public RetryTemplateBuilder maxAttempts(int maxAttempts) { @@ -117,12 +106,11 @@ public RetryTemplateBuilder maxAttempts(int maxAttempts) { } /** - * Allows retry if there is no more than {@code timeout} millis since first attempt. - *
- * Invocation of this method does not discard default exception classification rule, - * that is "retry only on {@link Exception} and it's subclasses". - * @param timeout whole execution timeout in milliseconds + * Retry until {@code timeout} has passed since the initial attempt. + * @param timeout timeout in milliseconds * @return this + * @throws IllegalArgumentException if timeout is {@literal <=} 0 or if another retry + * policy has already been selected. * @see TimeoutRetryPolicy * @deprecated Use {@link #withTimeout(long)} instead. */ @@ -132,13 +120,13 @@ public RetryTemplateBuilder withinMillis(long timeout) { } /** - * Retry until {@code timeout} has passed since the initial attempt. + * Retry until {@code timeoutMillis} has passed since the initial attempt. * @param timeoutMillis timeout in milliseconds * @return this - * @see TimeoutRetryPolicy * @throws IllegalArgumentException if timeout is {@literal <=} 0 or if another retry - * policy has already been configured. + * policy has already been selected. * @since 2.0.2 + * @see TimeoutRetryPolicy */ public RetryTemplateBuilder withTimeout(long timeoutMillis) { Assert.isTrue(timeoutMillis > 0, "timeoutMillis should be greater than 0"); @@ -151,10 +139,10 @@ public RetryTemplateBuilder withTimeout(long timeoutMillis) { * Retry until {@code timeout} has passed since the initial attempt. * @param timeout duration for how long retries should be attempted * @return this - * @see TimeoutRetryPolicy * @throws IllegalArgumentException if timeout is {@code null} or 0, or if another - * retry policy has already been configured. + * retry policy has already been selected. * @since 2.0.2 + * @see TimeoutRetryPolicy */ public RetryTemplateBuilder withTimeout(Duration timeout) { Assert.notNull(timeout, "timeout must not be null"); @@ -162,12 +150,10 @@ public RetryTemplateBuilder withTimeout(Duration timeout) { } /** - * Allows infinite retry, do not limit attempts by number or time. - *
- * Invocation of this method does not discard default exception classification rule, - * that is "retry only on {@link Exception} and it's subclasses". + * Retry actions infinitely. * @return this - * @see TimeoutRetryPolicy + * @throws IllegalArgumentException if another retry policy has already been selected. + * @see AlwaysRetryPolicy */ public RetryTemplateBuilder infiniteRetry() { Assert.isNull(this.baseRetryPolicy, "You have already selected another retry policy"); @@ -176,13 +162,12 @@ public RetryTemplateBuilder infiniteRetry() { } /** - * If flexibility of this builder is not enough for you, you can provide your own - * {@link RetryPolicy} via this method. - *
- * Invocation of this method does not discard default exception classification rule, - * that is "retry only on {@link Exception} and it's subclasses". - * @param policy will be directly set to resulting {@link RetryTemplate} + * Use the provided {@link RetryPolicy}. + * @param policy {@link RetryPolicy} to use * @return this + * @throws IllegalArgumentException if another backoff policy has already been + * selected, if any argument is {@code null}, or if another retry policy has already + * been selected. */ public RetryTemplateBuilder customPolicy(RetryPolicy policy) { Assert.notNull(policy, "Policy should not be null"); @@ -194,15 +179,18 @@ public RetryTemplateBuilder customPolicy(RetryPolicy policy) { /* ---------------- Configure backoff policy -------------- */ /** - * Use exponential backoff policy. The formula of backoff period: + * Use an exponential backoff policy. The formula for the backoff period is: *
* {@code currentInterval = Math.min(initialInterval * Math.pow(multiplier, retryNum), maxInterval)} *
- * (for first attempt retryNum = 0) - * @param initialInterval in milliseconds - * @param multiplier see the formula above - * @param maxInterval in milliseconds + * For the first attempt, {@code retryNum = 0}. + * @param initialInterval initial sleep duration in milliseconds + * @param multiplier backoff interval multiplier + * @param maxInterval maximum backoff duration in milliseconds * @return this + * @throws IllegalArgumentException if another backoff policy has already been + * selected, if {@code initialInterval} is {@literal <} 1, if {@code multiplier} is + * {@literal <=} 1, or if {@code maxInterval} {@literal <=} {@code initialInterval}. * @see ExponentialBackOffPolicy */ public RetryTemplateBuilder exponentialBackoff(long initialInterval, double multiplier, long maxInterval) { @@ -210,19 +198,21 @@ public RetryTemplateBuilder exponentialBackoff(long initialInterval, double mult } /** - * Use exponential backoff policy. The formula of backoff period: + * Use an exponential backoff policy. The formula for the backoff period is: *
* {@code currentInterval = Math.min(initialInterval * Math.pow(multiplier, retryNum), maxInterval)} *
- * (for first attempt retryNum = 0) + * For the first attempt, {@code retryNum = 0}. * @param initialInterval initial sleep duration * @param multiplier backoff interval multiplier * @param maxInterval maximum backoff duration * @return this - * @see ExponentialBackOffPolicy - * @throws IllegalArgumentException if initialInterval is {@code null}, multiplier is - * {@literal <=} 1, or if maxInterval is {@code null} + * @throws IllegalArgumentException if another backoff policy has already been + * selected, if {@code initialInterval} is {@code null} or less than 1 millisecond, + * multiplier is {@literal <=} 1, or if {@code maxInterval} is {@code null} or + * {@literal <=} {@code initialInterval}. * @since 2.0.2 + * @see ExponentialBackOffPolicy */ public RetryTemplateBuilder exponentialBackoff(Duration initialInterval, double multiplier, Duration maxInterval) { Assert.notNull(initialInterval, "initialInterval must not be null"); @@ -231,17 +221,21 @@ public RetryTemplateBuilder exponentialBackoff(Duration initialInterval, double } /** - * Use exponential backoff policy. The formula of backoff period (without randomness): + * Use an exponential backoff policy. The formula for the backoff period is (without + * randomness): *
* {@code currentInterval = Math.min(initialInterval * Math.pow(multiplier, retryNum), maxInterval)} *
- * (for first attempt retryNum = 0) - * @param initialInterval in milliseconds - * @param multiplier see the formula above - * @param maxInterval in milliseconds - * @param withRandom adds some randomness to backoff intervals. For details, see - * {@link ExponentialRandomBackOffPolicy} + * For the first attempt, {@code retryNum = 0}. + * @param initialInterval initial sleep duration in milliseconds + * @param multiplier backoff interval multiplier + * @param maxInterval maximum backoff duration in milliseconds + * @param withRandom whether to use a {@link ExponentialRandomBackOffPolicy} (if + * {@code true}) or not * @return this + * @throws IllegalArgumentException if another backoff policy has already been + * selected, if {@code initialInterval} is {@literal <} 1, if {@code multiplier} is + * {@literal <=} 1, or if {@code maxInterval} {@literal <=} {@code initialInterval}. * @see ExponentialBackOffPolicy * @see ExponentialRandomBackOffPolicy */ @@ -261,22 +255,25 @@ public RetryTemplateBuilder exponentialBackoff(long initialInterval, double mult } /** - * Use exponential backoff policy. The formula of backoff period (without randomness): + * Use an exponential backoff policy. The formula for the backoff period is (without + * randomness): *
* {@code currentInterval = Math.min(initialInterval * Math.pow(multiplier, retryNum), maxInterval)} *
- * (for first attempt retryNum = 0) + * For the first attempt, {@code retryNum = 0}. * @param initialInterval initial sleep duration * @param multiplier backoff interval multiplier * @param maxInterval maximum backoff duration - * @param withRandom adds some randomness to backoff intervals. For details, see - * {@link ExponentialRandomBackOffPolicy} + * @param withRandom whether to use a {@link ExponentialRandomBackOffPolicy} (if + * {@code true}) or not * @return this + * @throws IllegalArgumentException if another backoff policy has already been + * selected, if {@code initialInterval} is {@code null} or less than 1 millisecond, if + * {@code multiplier} is {@literal <=} 1, or if {@code maxInterval} is {@code null} or + * {@literal <=} {@code initialInterval}. + * @since 2.0.2 * @see ExponentialBackOffPolicy * @see ExponentialRandomBackOffPolicy - * @throws IllegalArgumentException if initialInterval is {@code null}, multiplier is - * {@literal <=} 1, or maxInterval is {@code null} - * @since 2.0.2 */ public RetryTemplateBuilder exponentialBackoff(Duration initialInterval, double multiplier, Duration maxInterval, boolean withRandom) { @@ -286,9 +283,11 @@ public RetryTemplateBuilder exponentialBackoff(Duration initialInterval, double } /** - * Perform each retry after fixed amount of time. + * Perform each retry after a fixed amount of time. * @param interval fixed interval in milliseconds * @return this + * @throws IllegalArgumentException if another backoff policy has already been + * selected, or if {@code interval} is {@literal <} 1. * @see FixedBackOffPolicy */ public RetryTemplateBuilder fixedBackoff(long interval) { @@ -304,10 +303,10 @@ public RetryTemplateBuilder fixedBackoff(long interval) { * Perform each retry after fixed amount of time. * @param interval fixed backoff duration * @return this - * @see FixedBackOffPolicy * @throws IllegalArgumentException if another backoff policy has already been - * configured, interval is {@code null} or less than 1 millisecond + * selected, or if {@code interval} is {@code null} or less than 1 millisecond * @since 2.0.2 + * @see FixedBackOffPolicy */ public RetryTemplateBuilder fixedBackoff(Duration interval) { Assert.notNull(interval, "interval must not be null"); @@ -319,10 +318,13 @@ public RetryTemplateBuilder fixedBackoff(Duration interval) { } /** - * Use {@link UniformRandomBackOffPolicy}, see it's doc for details. - * @param minInterval in milliseconds - * @param maxInterval in milliseconds + * Use {@link UniformRandomBackOffPolicy}. + * @param minInterval minimal interval in milliseconds + * @param maxInterval maximal interval in milliseconds * @return this + * @throws IllegalArgumentException if another backoff policy has already been + * selected, if {@code minInterval} is {@literal <} 1, {@code maxInterval} is + * {@literal <} 1, or if {@code maxInterval} is {@literal <=} {@code minInterval}. * @see UniformRandomBackOffPolicy */ public RetryTemplateBuilder uniformRandomBackoff(long minInterval, long maxInterval) { @@ -342,11 +344,12 @@ public RetryTemplateBuilder uniformRandomBackoff(long minInterval, long maxInter * @param minInterval minimum backoff duration * @param maxInterval maximum backoff duration * @return this - * @see UniformRandomBackOffPolicy - * @throws IllegalArgumentException if minInterval is {@code null} or {@literal <} 1, - * maxInterval is {@code null} or {@literal <} 1, maxInterval {@literal >=} - * minInterval or if another backoff policy has already been configured. + * @throws IllegalArgumentException if another backoff policy has already been + * selected, if {@code minInterval} is {@code null} or {@literal <} 1, + * {@code maxInterval} is {@code null} or less than 1 millisecond, or if + * {@code maxInterval} {@literal <=} {@code minInterval}. * @since 2.0.2 + * @see UniformRandomBackOffPolicy */ public RetryTemplateBuilder uniformRandomBackoff(Duration minInterval, Duration maxInterval) { Assert.notNull(minInterval, "minInterval must not be null"); @@ -355,8 +358,10 @@ public RetryTemplateBuilder uniformRandomBackoff(Duration minInterval, Duration } /** - * Do not pause between attempts, retry immediately. + * Retry immediately without pausing between attempts. * @return this + * @throws IllegalArgumentException if another backoff policy has already been + * selected. * @see NoBackOffPolicy */ public RetryTemplateBuilder noBackoff() { @@ -366,9 +371,11 @@ public RetryTemplateBuilder noBackoff() { } /** - * You can provide your own {@link BackOffPolicy} via this method. - * @param backOffPolicy will be directly set to resulting {@link RetryTemplate} + * Use the provided {@link BackOffPolicy}. + * @param backOffPolicy {@link BackOffPolicy} to use * @return this + * @throws IllegalArgumentException if {@code backOffPolicy} is null or if another + * backoff policy has already been selected. */ public RetryTemplateBuilder customBackoff(BackOffPolicy backOffPolicy) { Assert.isNull(this.backOffPolicy, "You have already selected backoff policy"); @@ -380,16 +387,15 @@ public RetryTemplateBuilder customBackoff(BackOffPolicy backOffPolicy) { /* ---------------- Configure exception classifier -------------- */ /** - * Add a throwable to the while list of retryable exceptions. + * Add the {@link Throwable} and its subclasses to the list of exceptions that cause a + * retry. *
- * Warn: touching this method drops default {@code retryOn(Exception.class)} and you - * should configure whole classifier from scratch. - *
- * You should select the way you want to configure exception classifier: white list or - * black list. If you choose white list - use this method, if black - use - * {@link #notRetryOn(Class)} or {@link #notRetryOn(List)} - * @param throwable to be retryable (with it's subclasses) + * {@code retryOn()} and {@code notRetryOn()} are mutually exclusive. Trying to use + * both causes an {@link IllegalArgumentException}. + * @param throwable that causes a retry * @return this + * @throws IllegalArgumentException if {@code throwable} is {@code null}, or if + * {@link #notRetryOn} has already been used. * @see BinaryExceptionClassifierBuilder#retryOn * @see BinaryExceptionClassifier */ @@ -399,16 +405,15 @@ public RetryTemplateBuilder retryOn(Class extends Throwable> throwable) { } /** - * Add a throwable to the black list of retryable exceptions. - *
- * Warn: touching this method drops default {@code retryOn(Exception.class)} and you - * should configure whole classifier from scratch. + * Add the {@link Throwable} and its subclasses to the list of exceptions that do not + * cause a retry. *
- * You should select the way you want to configure exception classifier: white list or - * black list. If you choose black list - use this method, if white - use - * {@link #retryOn(Class)} or {@link #retryOn(List)} - * @param throwable to be not retryable (with it's subclasses) + * {@code retryOn()} and {@code notRetryOn()} are mutually exclusive. Trying to use + * both causes an {@link IllegalArgumentException}. + * @param throwable that does not cause a retry * @return this + * @throws IllegalArgumentException if {@code throwable} is {@code null}, or if + * {@link #retryOn} has already been used. * @see BinaryExceptionClassifierBuilder#notRetryOn * @see BinaryExceptionClassifier */ @@ -418,16 +423,14 @@ public RetryTemplateBuilder notRetryOn(Class extends Throwable> throwable) { } /** - * Add all throwables to the while list of retryable exceptions. - *
- * Warn: touching this method drops default {@code retryOn(Exception.class)} and you - * should configure whole classifier from scratch. + * Add the list of {@link Throwable} classes and their subclasses to the list of + * exceptions that cause a retry. *
- * You should select the way you want to configure exception classifier: white list or
- * black list. If you choose white list - use this method, if black - use
- * {@link #notRetryOn(Class)} or {@link #notRetryOn(List)}
- * @param throwables to be retryable (with it's subclasses)
+ * {@code retryOn()} and {@code notRetryOn()} are mutually exclusive. Trying to use
+ * both causes an {@link IllegalArgumentException}.
+ * @param throwables that cause a retry
* @return this
+ * @throws IllegalArgumentException if {@link #notRetryOn} has already been used.
* @since 1.3.2
* @see BinaryExceptionClassifierBuilder#retryOn
* @see BinaryExceptionClassifier
@@ -440,16 +443,14 @@ public RetryTemplateBuilder retryOn(List
- * Warn: touching this method drops default {@code retryOn(Exception.class)} and you
- * should configure whole classifier from scratch.
- *
- * You should select the way you want to configure exception classifier: white list or
- * black list. If you choose black list - use this method, if white - use
- * {@link #retryOn(Class)} or {@link #retryOn(List)}
- * @param throwables to be not retryable (with it's subclasses)
+ * {@code retryOn()} and {@code notRetryOn()} are mutually exclusive. Trying to use
+ * both causes an {@link IllegalArgumentException}.
+ * @param throwables that do not cause a retry
* @return this
+ * @throws IllegalArgumentException if {@link #retryOn} has already been used.
* @since 1.3.2
* @see BinaryExceptionClassifierBuilder#notRetryOn
* @see BinaryExceptionClassifier
@@ -462,12 +463,20 @@ public RetryTemplateBuilder notRetryOn(List
+ * Suppose the following {@code RetryTemplate}:
+ * It will act on code that throws an {@link java.io.IOException}, for example,
+ * {@code throw new IOException()}. But it will not retry the action if the
+ * {@link java.io.IOException} is wrapped in another exception, for example,
+ * {@code throw new MyException(new IOException())}. However, this
+ * {@link RetryTemplate} will:
+ * Supports building multiple instances. However, it is not possible to change the
+ * configuration between multiple {@code build()} calls.
+ *
+ * The {@code retryPolicy} of the returned {@link RetryTemplate} is always a
+ * {@link CompositeRetryPolicy} that consists of one base policy and of
+ * {@link BinaryExceptionClassifierRetryPolicy} to enable exception classification
+ * regardless of the base policy.
* @return new instance of {@link RetryTemplate}
*/
public RetryTemplate build() {
diff --git a/src/test/java/org/springframework/retry/support/RetryTemplateBuilderTests.java b/src/test/java/org/springframework/retry/support/RetryTemplateBuilderTests.java
index 58799ec4..f0984179 100644
--- a/src/test/java/org/springframework/retry/support/RetryTemplateBuilderTests.java
+++ b/src/test/java/org/springframework/retry/support/RetryTemplateBuilderTests.java
@@ -308,7 +308,7 @@ public void testValidateInitAndMax() {
}
@Test
- public void testValidateMeaninglessMultipier() {
+ public void testValidateMeaninglessMultiplier() {
assertThatIllegalArgumentException()
.isThrownBy(() -> RetryTemplate.builder().exponentialBackoff(100, 1, 200).build());
}
{@code
+ * Enable examining exception causes for {@link Throwable} instances that cause a
+ * retry.
+ *
{@code
* RetryTemplate.builder()
* .retryOn(IOException.class)
* .build()
- * }
but this will retry: {@code
+ * }
+ * {@code
* RetryTemplate.builder()
* .retryOn(IOException.class)
* .traversingCauses()
@@ -484,10 +493,10 @@ public RetryTemplateBuilder traversingCauses() {
/* ---------------- Add listeners -------------- */
/**
- * Appends provided {@code listener} to {@link RetryTemplate}'s listener list.
+ * Append the provided {@code listener} to {@link RetryTemplate}'s list of listeners.
* @param listener to be appended
* @return this
- * @see RetryTemplate
+ * @throws IllegalArgumentException if {@code listener} is {@code null}.
* @see RetryListener
*/
public RetryTemplateBuilder withListener(RetryListener listener) {
@@ -497,10 +506,10 @@ public RetryTemplateBuilder withListener(RetryListener listener) {
}
/**
- * Appends all provided {@code listeners} to {@link RetryTemplate}'s listener list.
+ * Append all provided {@code listeners} to {@link RetryTemplate}'s list of listeners.
* @param listeners to be appended
* @return this
- * @see RetryTemplate
+ * @throws IllegalArgumentException if any of the {@code listeners} is {@code null}.
* @see RetryListener
*/
public RetryTemplateBuilder withListeners(List