Implement toString() in RetryPolicy and RetryExecution implementations
Build and Deploy Snapshot / Build and Deploy Snapshot (push) Waiting to run Details
Build and Deploy Snapshot / Verify (push) Blocked by required conditions Details
Deploy Docs / Dispatch docs deployment (push) Waiting to run Details

Closes gh-35029
This commit is contained in:
Sam Brannen 2025-06-11 19:07:56 +02:00
parent bc967842f6
commit bfd3dc2676
6 changed files with 174 additions and 29 deletions

View File

@ -21,10 +21,10 @@ 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
* configured maximum number.
* A {@link RetryPolicy} based on a maximum number of retry attempts.
*
* @author Mahmoud Ben Hassine
* @author Sam Brannen
* @since 7.0
*/
public class MaxRetryAttemptsPolicy implements RetryPolicy {
@ -56,11 +56,6 @@ public class MaxRetryAttemptsPolicy implements RetryPolicy {
setMaxRetryAttempts(maxRetryAttempts);
}
@Override
public RetryExecution start() {
return new MaxRetryAttemptsPolicyExecution();
}
/**
* Set the maximum number of retry attempts.
* @param maxRetryAttempts the maximum number of retry attempts; must be greater
@ -71,6 +66,16 @@ public class MaxRetryAttemptsPolicy implements RetryPolicy {
this.maxRetryAttempts = maxRetryAttempts;
}
@Override
public RetryExecution start() {
return new MaxRetryAttemptsPolicyExecution();
}
@Override
public String toString() {
return "MaxRetryAttemptsPolicy[maxRetryAttempts=%d]".formatted(this.maxRetryAttempts);
}
/**
* A {@link RetryExecution} based on a maximum number of retry attempts.
@ -83,6 +88,13 @@ public class MaxRetryAttemptsPolicy implements RetryPolicy {
public boolean shouldRetry(Throwable throwable) {
return (this.retryAttempts++ < MaxRetryAttemptsPolicy.this.maxRetryAttempts);
}
@Override
public String toString() {
return "MaxRetryAttemptsPolicyExecution[retryAttempts=%d, maxRetryAttempts=%d]"
.formatted(this.retryAttempts, MaxRetryAttemptsPolicy.this.maxRetryAttempts);
}
}
}

View File

@ -24,9 +24,10 @@ import org.springframework.core.retry.RetryPolicy;
import org.springframework.util.Assert;
/**
* A {@link RetryPolicy} based on a maximum retry duration.
* A {@link RetryPolicy} based on a maximum retry {@link Duration}.
*
* @author Mahmoud Ben Hassine
* @author Sam Brannen
* @since 7.0
*/
public class MaxRetryDurationPolicy implements RetryPolicy {
@ -42,7 +43,7 @@ public class MaxRetryDurationPolicy implements RetryPolicy {
/**
* Create a new {@code MaxRetryDurationPolicy} with the default maximum retry
* duration.
* {@link Duration}.
* @see #DEFAULT_MAX_RETRY_DURATION
*/
public MaxRetryDurationPolicy() {
@ -50,20 +51,15 @@ public class MaxRetryDurationPolicy implements RetryPolicy {
/**
* Create a new {@code MaxRetryDurationPolicy} with the specified maximum retry
* duration.
* {@link Duration}.
* @param maxRetryDuration the maximum retry duration; must be positive
*/
public MaxRetryDurationPolicy(Duration maxRetryDuration) {
setMaxRetryDuration(maxRetryDuration);
}
@Override
public RetryExecution start() {
return new MaxRetryDurationPolicyExecution();
}
/**
* Set the maximum retry duration.
* Set the maximum retry {@link Duration}.
* @param maxRetryDuration the maximum retry duration; must be positive
*/
public void setMaxRetryDuration(Duration maxRetryDuration) {
@ -72,6 +68,17 @@ public class MaxRetryDurationPolicy implements RetryPolicy {
this.maxRetryDuration = maxRetryDuration;
}
@Override
public RetryExecution start() {
return new MaxRetryDurationPolicyExecution();
}
@Override
public String toString() {
return "MaxRetryDurationPolicy[maxRetryDuration=%dms]".formatted(this.maxRetryDuration.toMillis());
}
/**
* A {@link RetryExecution} based on a maximum retry duration.
*/
@ -84,6 +91,13 @@ public class MaxRetryDurationPolicy implements RetryPolicy {
Duration currentRetryDuration = Duration.between(this.retryStartTime, LocalDateTime.now());
return currentRetryDuration.compareTo(MaxRetryDurationPolicy.this.maxRetryDuration) <= 0;
}
@Override
public String toString() {
return "MaxRetryDurationPolicyExecution[retryStartTime=%s, maxRetryDuration=%dms]"
.formatted(this.retryStartTime, MaxRetryDurationPolicy.this.maxRetryDuration.toMillis());
}
}
}

View File

@ -25,6 +25,7 @@ import org.springframework.core.retry.RetryPolicy;
* A {@link RetryPolicy} based on a {@link Predicate}.
*
* @author Mahmoud Ben Hassine
* @author Sam Brannen
* @since 7.0
*/
public class PredicateRetryPolicy implements RetryPolicy {
@ -44,7 +45,31 @@ public class PredicateRetryPolicy implements RetryPolicy {
@Override
public RetryExecution start() {
return this.predicate::test;
return new PredicateRetryPolicyExecution();
}
@Override
public String toString() {
return "PredicateRetryPolicy[predicate=%s]".formatted(this.predicate.getClass().getSimpleName());
}
/**
* A {@link RetryExecution} based on a {@link Predicate}.
*/
private class PredicateRetryPolicyExecution implements RetryExecution {
@Override
public boolean shouldRetry(Throwable throwable) {
return PredicateRetryPolicy.this.predicate.test(throwable);
}
@Override
public String toString() {
return "PredicateRetryPolicyExecution[predicate=%s]"
.formatted(PredicateRetryPolicy.this.predicate.getClass().getSimpleName());
}
}
}

View File

@ -25,9 +25,11 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
import static org.mockito.Mockito.mock;
/**
* Tests for {@link MaxRetryAttemptsPolicy}.
* Tests for {@link MaxRetryAttemptsPolicy} and its {@link RetryExecution}.
*
* @author Mahmoud Ben Hassine
* @author Sam Brannen
* @since 7.0
*/
class MaxRetryAttemptsPolicyTests {
@ -44,14 +46,69 @@ class MaxRetryAttemptsPolicyTests {
assertThat(retryExecution.shouldRetry(throwable)).isTrue();
assertThat(retryExecution.shouldRetry(throwable)).isTrue();
assertThat(retryExecution.shouldRetry(throwable)).isTrue();
assertThat(retryExecution.shouldRetry(throwable)).isFalse();
assertThat(retryExecution.shouldRetry(throwable)).isFalse();
}
@Test
void customMaxRetryAttempts() {
// given
MaxRetryAttemptsPolicy retryPolicy = new MaxRetryAttemptsPolicy(2);
Throwable throwable = mock();
// when
RetryExecution retryExecution = retryPolicy.start();
// then
assertThat(retryExecution.shouldRetry(throwable)).isTrue();
assertThat(retryExecution.shouldRetry(throwable)).isTrue();
assertThat(retryExecution.shouldRetry(throwable)).isFalse();
assertThat(retryExecution.shouldRetry(throwable)).isFalse();
}
@Test
void invalidMaxRetryAttempts() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new MaxRetryAttemptsPolicy(0))
.withMessage("Max retry attempts must be greater than zero");
assertThatIllegalArgumentException()
.isThrownBy(() -> new MaxRetryAttemptsPolicy(-1))
.withMessage("Max retry attempts must be greater than zero");
}
@Test
void toStringImplementations() {
MaxRetryAttemptsPolicy policy1 = new MaxRetryAttemptsPolicy();
MaxRetryAttemptsPolicy policy2 = new MaxRetryAttemptsPolicy(1);
assertThat(policy1).asString().isEqualTo("MaxRetryAttemptsPolicy[maxRetryAttempts=3]");
assertThat(policy2).asString().isEqualTo("MaxRetryAttemptsPolicy[maxRetryAttempts=1]");
RetryExecution retryExecution = policy1.start();
assertThat(retryExecution).asString()
.isEqualTo("MaxRetryAttemptsPolicyExecution[retryAttempts=0, maxRetryAttempts=3]");
assertThat(retryExecution.shouldRetry(mock())).isTrue();
assertThat(retryExecution).asString()
.isEqualTo("MaxRetryAttemptsPolicyExecution[retryAttempts=1, maxRetryAttempts=3]");
assertThat(retryExecution.shouldRetry(mock())).isTrue();
assertThat(retryExecution).asString()
.isEqualTo("MaxRetryAttemptsPolicyExecution[retryAttempts=2, maxRetryAttempts=3]");
assertThat(retryExecution.shouldRetry(mock())).isTrue();
assertThat(retryExecution).asString()
.isEqualTo("MaxRetryAttemptsPolicyExecution[retryAttempts=3, maxRetryAttempts=3]");
assertThat(retryExecution.shouldRetry(mock())).isFalse();
assertThat(retryExecution).asString()
.isEqualTo("MaxRetryAttemptsPolicyExecution[retryAttempts=4, maxRetryAttempts=3]");
assertThat(retryExecution.shouldRetry(mock())).isFalse();
assertThat(retryExecution).asString()
.isEqualTo("MaxRetryAttemptsPolicyExecution[retryAttempts=5, maxRetryAttempts=3]");
}
}

View File

@ -20,12 +20,17 @@ import java.time.Duration;
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.assertThatIllegalArgumentException;
/**
* Tests for {@link MaxRetryDurationPolicy}.
* Tests for {@link MaxRetryDurationPolicy} and its {@link RetryExecution}.
*
* @author Mahmoud Ben Hassine
* @author Sam Brannen
* @since 7.0
*/
class MaxRetryDurationPolicyTests {
@ -36,4 +41,18 @@ class MaxRetryDurationPolicyTests {
.withMessage("Max retry duration must be positive");
}
@Test
void toStringImplementations() {
MaxRetryDurationPolicy policy1 = new MaxRetryDurationPolicy();
MaxRetryDurationPolicy policy2 = new MaxRetryDurationPolicy(Duration.ofSeconds(1));
assertThat(policy1).asString().isEqualTo("MaxRetryDurationPolicy[maxRetryDuration=3000ms]");
assertThat(policy2).asString().isEqualTo("MaxRetryDurationPolicy[maxRetryDuration=1000ms]");
assertThat(policy1.start()).asString()
.matches("MaxRetryDurationPolicyExecution\\[retryStartTime=.+, maxRetryDuration=3000ms\\]");
assertThat(policy2.start()).asString()
.matches("MaxRetryDurationPolicyExecution\\[retryStartTime=.+, maxRetryDuration=1000ms\\]");
}
}

View File

@ -25,28 +25,46 @@ import org.springframework.core.retry.RetryExecution;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link PredicateRetryPolicy}.
* Tests for {@link PredicateRetryPolicy} and its {@link RetryExecution}.
*
* @author Mahmoud Ben Hassine
* @author Sam Brannen
* @since 7.0
*/
class PredicateRetryPolicyTests {
@Test
void predicateRetryPolicy() {
// given
class MyException extends Exception {
@java.io.Serial
private static final long serialVersionUID = 1L;
}
Predicate<Throwable> predicate = MyException.class::isInstance;
Predicate<Throwable> predicate = NumberFormatException.class::isInstance;
PredicateRetryPolicy retryPolicy = new PredicateRetryPolicy(predicate);
// when
RetryExecution retryExecution = retryPolicy.start();
// then
assertThat(retryExecution.shouldRetry(new MyException())).isTrue();
assertThat(retryExecution.shouldRetry(new NumberFormatException())).isTrue();
assertThat(retryExecution.shouldRetry(new IllegalStateException())).isFalse();
}
@Test
void toStringImplementations() {
PredicateRetryPolicy policy1 = new PredicateRetryPolicy(NumberFormatException.class::isInstance);
PredicateRetryPolicy policy2 = new PredicateRetryPolicy(new NumberFormatExceptionMatcher());
assertThat(policy1).asString().matches("PredicateRetryPolicy\\[predicate=PredicateRetryPolicyTests.+?Lambda.+?\\]");
assertThat(policy2).asString().isEqualTo("PredicateRetryPolicy[predicate=NumberFormatExceptionMatcher]");
assertThat(policy1.start()).asString()
.matches("PredicateRetryPolicyExecution\\[predicate=PredicateRetryPolicyTests.+?Lambda.+?\\]");
assertThat(policy2.start()).asString()
.isEqualTo("PredicateRetryPolicyExecution[predicate=NumberFormatExceptionMatcher]");
}
private static class NumberFormatExceptionMatcher implements Predicate<Throwable> {
@Override
public boolean test(Throwable throwable) {
return (throwable instanceof NumberFormatException);
}
}
}