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 902a46a81db..01bb717a093 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 @@ -34,7 +34,7 @@ public final class PredicateRetryPolicy implements RetryPolicy { /** - * Create a new {@code PredicateRetryPolicy} with the given predicate. + * Create a new {@code PredicateRetryPolicy} with the given {@link Predicate}. * @param predicate the predicate to use for determining whether to retry an * operation based on a given {@link Throwable} */ 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 13a9e9ec86e..adc3a45c032 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 @@ -17,7 +17,9 @@ package org.springframework.core.retry; import java.time.Duration; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.util.backoff.FixedBackOff; @@ -36,95 +38,99 @@ class RetryTemplateTests { private final RetryTemplate retryTemplate = new RetryTemplate(); - @Test - void retryWithSuccess() throws Exception { - Retryable retryable = new Retryable<>() { - int failure; + @BeforeEach + void configureTemplate() { + this.retryTemplate.setBackOffPolicy(new FixedBackOff(Duration.ofMillis(10))); + } - @Override - public String execute() throws Exception { - if (failure++ < 2) { - throw new Exception("Error while invoking greeting service"); - } - return "hello world"; - } + @Test + void retryWithImmediateSuccess() throws Exception { + AtomicInteger invocationCount = new AtomicInteger(); + Retryable retryable = () -> { + invocationCount.incrementAndGet(); + return "always succeeds"; + }; - @Override - public String getName() { - return "greeting service"; + assertThat(invocationCount).hasValue(0); + assertThat(retryTemplate.execute(retryable)).isEqualTo("always succeeds"); + assertThat(invocationCount).hasValue(1); + } + + @Test + void retryWithSuccessAfterInitialFailures() throws Exception { + AtomicInteger invocationCount = new AtomicInteger(); + Retryable retryable = () -> { + if (invocationCount.incrementAndGet() <= 2) { + throw new Exception("Boom!"); } + return "finally succeeded"; }; - retryTemplate.setBackOffPolicy(new FixedBackOff(Duration.ofMillis(10))); - - assertThat(retryTemplate.execute(retryable)).isEqualTo("hello world"); + assertThat(invocationCount).hasValue(0); + assertThat(retryTemplate.execute(retryable)).isEqualTo("finally succeeded"); + assertThat(invocationCount).hasValue(3); } @Test - void retryWithFailure() { - Exception exception = new Exception("Error while invoking greeting service"); + void retryWithExhaustedPolicy() { + AtomicInteger invocationCount = new AtomicInteger(); + RuntimeException exception = new RuntimeException("Boom!"); Retryable retryable = new Retryable<>() { @Override - public String execute() throws Exception { + public String execute() { + invocationCount.incrementAndGet(); throw exception; } @Override public String getName() { - return "greeting service"; + return "test"; } }; - retryTemplate.setBackOffPolicy(new FixedBackOff(Duration.ofMillis(10))); - + assertThat(invocationCount).hasValue(0); assertThatExceptionOfType(RetryException.class) .isThrownBy(() -> retryTemplate.execute(retryable)) - .withMessage("Retry policy for operation 'greeting service' exhausted; aborting execution") + .withMessage("Retry policy for operation 'test' exhausted; aborting execution") .withCause(exception); + // 4 = 1 initial invocation + 3 retry attempts + assertThat(invocationCount).hasValue(4); } @Test - void retrySpecificException() { - - @SuppressWarnings("serial") - class TechnicalException extends Exception { - public TechnicalException(String message) { - super(message); - } - } - - TechnicalException technicalException = new TechnicalException("Error while invoking greeting service"); + void retryWithFailingRetryableAndCustomRetryPolicy() { + AtomicInteger invocationCount = new AtomicInteger(); + RuntimeException exception = new NumberFormatException(); Retryable retryable = new Retryable<>() { @Override - public String execute() throws TechnicalException { - throw technicalException; + public String execute() { + invocationCount.incrementAndGet(); + throw exception; } @Override public String getName() { - return "greeting service"; - } - }; - - RetryPolicy retryPolicy = () -> new RetryExecution() { - int retryAttempts; - - @Override - public boolean shouldRetry(Throwable throwable) { - return (this.retryAttempts++ < 3 && throwable instanceof TechnicalException); + return "always fails"; } }; + AtomicInteger retryCount = new AtomicInteger(); + // Custom RetryPolicy that only retries for a NumberFormatException and max 5 retry attempts. + RetryPolicy retryPolicy = () -> throwable -> (retryCount.incrementAndGet() <= 5 && throwable instanceof NumberFormatException); retryTemplate.setRetryPolicy(retryPolicy); - retryTemplate.setBackOffPolicy(new FixedBackOff(Duration.ofMillis(10))); + assertThat(invocationCount).hasValue(0); + assertThat(retryCount).hasValue(0); assertThatExceptionOfType(RetryException.class) .isThrownBy(() -> retryTemplate.execute(retryable)) - .withMessage("Retry policy for operation 'greeting service' exhausted; aborting execution") - .withCause(technicalException); + .withMessage("Retry policy for operation 'always fails' exhausted; aborting execution") + .withCause(exception); + // 6 = 1 initial invocation + 5 retry attempts + assertThat(invocationCount).hasValue(6); + assertThat(retryCount).hasValue(6); } }