diff --git a/src/main/java/org/springframework/retry/annotation/AnnotationAwareRetryOperationsInterceptor.java b/src/main/java/org/springframework/retry/annotation/AnnotationAwareRetryOperationsInterceptor.java index 6201a07..eb68953 100644 --- a/src/main/java/org/springframework/retry/annotation/AnnotationAwareRetryOperationsInterceptor.java +++ b/src/main/java/org/springframework/retry/annotation/AnnotationAwareRetryOperationsInterceptor.java @@ -348,12 +348,13 @@ public class AnnotationAwareRetryOperationsInterceptor implements IntroductionIn for (Class type : excludes) { policyMap.put(type, false); } + boolean retryNotExcluded = includes.length == 0; if (hasExpression) { - return new ExpressionRetryPolicy(maxAttempts, policyMap, true, exceptionExpression) + return new ExpressionRetryPolicy(maxAttempts, policyMap, true, exceptionExpression, retryNotExcluded) .withBeanFactory(this.beanFactory); } else { - return new SimpleRetryPolicy(maxAttempts, policyMap, true); + return new SimpleRetryPolicy(maxAttempts, policyMap, true, retryNotExcluded); } } diff --git a/src/main/java/org/springframework/retry/annotation/CircuitBreaker.java b/src/main/java/org/springframework/retry/annotation/CircuitBreaker.java index 478c2ab..706a453 100644 --- a/src/main/java/org/springframework/retry/annotation/CircuitBreaker.java +++ b/src/main/java/org/springframework/retry/annotation/CircuitBreaker.java @@ -54,7 +54,8 @@ public @interface CircuitBreaker { /** * Exception types that are not retryable. Defaults to empty (and if includes is also * empty all exceptions are retried). - * @return exception types to retry + * If includes is empty but excludes is not, all not excluded exceptions are retried + * @return exception types not to retry */ Class[] exclude() default {}; diff --git a/src/main/java/org/springframework/retry/annotation/Retryable.java b/src/main/java/org/springframework/retry/annotation/Retryable.java index 0b80d07..e970b7e 100644 --- a/src/main/java/org/springframework/retry/annotation/Retryable.java +++ b/src/main/java/org/springframework/retry/annotation/Retryable.java @@ -60,7 +60,8 @@ public @interface Retryable { /** * Exception types that are not retryable. Defaults to empty (and if includes is also * empty all exceptions are retried). - * @return exception types to retry + * If includes is empty but excludes is not, all not excluded exceptions are retried + * @return exception types not to retry */ Class[] exclude() default {}; diff --git a/src/main/java/org/springframework/retry/policy/ExpressionRetryPolicy.java b/src/main/java/org/springframework/retry/policy/ExpressionRetryPolicy.java index 88e8843..b46d5e6 100644 --- a/src/main/java/org/springframework/retry/policy/ExpressionRetryPolicy.java +++ b/src/main/java/org/springframework/retry/policy/ExpressionRetryPolicy.java @@ -83,10 +83,11 @@ public class ExpressionRetryPolicy extends SimpleRetryPolicy implements BeanFact * @param retryableExceptions the exceptions * @param traverseCauses true to examine causes * @param expressionString the expression. + * @param defaultValue the default action */ public ExpressionRetryPolicy(int maxAttempts, Map, Boolean> retryableExceptions, - boolean traverseCauses, String expressionString) { - super(maxAttempts, retryableExceptions, traverseCauses); + boolean traverseCauses, String expressionString, boolean defaultValue) { + super(maxAttempts, retryableExceptions, traverseCauses, defaultValue); Assert.notNull(expressionString, "'expressionString' cannot be null"); this.expression = new SpelExpressionParser().parseExpression(expressionString, PARSER_CONTEXT); } diff --git a/src/test/java/org/springframework/retry/annotation/EnableRetryTests.java b/src/test/java/org/springframework/retry/annotation/EnableRetryTests.java index 6b19cf0..e68d5da 100644 --- a/src/test/java/org/springframework/retry/annotation/EnableRetryTests.java +++ b/src/test/java/org/springframework/retry/annotation/EnableRetryTests.java @@ -130,6 +130,26 @@ public class EnableRetryTests { context.close(); } + @Test + public void excludesOnly() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( + TestConfiguration.class); + ExcludesOnlyService service = context.getBean(ExcludesOnlyService.class); + service.setExceptionToThrow(new IllegalStateException()); + try { + service.service(); + fail("Expected IllegalStateException"); + } + catch (IllegalStateException e) { + } + assertEquals(1, service.getCount()); + + service.setExceptionToThrow(new IllegalArgumentException()); + service.service(); + assertEquals(3, service.getCount()); + context.close(); + } + @Test public void stateful() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( @@ -297,6 +317,11 @@ public class EnableRetryTests { return new ExcludesService(); } + @Bean + public ExcludesOnlyService excludesOnly() { + return new ExcludesOnlyService(); + } + @Bean public MethodInterceptor retryInterceptor() { return RetryInterceptorBuilder.stateless().maxAttempts(5).build(); @@ -441,6 +466,27 @@ public class EnableRetryTests { } + protected static class ExcludesOnlyService { + + private int count = 0; + private RuntimeException exceptionToThrow; + + @Retryable(exclude = IllegalStateException.class) + public void service() { + if (count++ < 2) { + throw exceptionToThrow; + } + } + + public int getCount() { + return count; + } + + public void setExceptionToThrow(RuntimeException exceptionToThrow) { + this.exceptionToThrow = exceptionToThrow; + } + } + protected static class StatefulService { private int count = 0;