diff --git a/src/main/java/org/springframework/retry/annotation/AnnotationAwareRetryOperationsInterceptor.java b/src/main/java/org/springframework/retry/annotation/AnnotationAwareRetryOperationsInterceptor.java index cab4032..4dfa69d 100644 --- a/src/main/java/org/springframework/retry/annotation/AnnotationAwareRetryOperationsInterceptor.java +++ b/src/main/java/org/springframework/retry/annotation/AnnotationAwareRetryOperationsInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -252,6 +252,7 @@ public class AnnotationAwareRetryOperationsInterceptor implements IntroductionIn resetTimeout(breaker, circuit); template.setRetryPolicy(breaker); template.setBackOffPolicy(new NoBackOffPolicy()); + template.setThrowLastExceptionOnExhausted(circuit.throwLastExceptionOnExhausted()); String label = circuit.label(); if (!StringUtils.hasText(label)) { label = method.toGenericString(); diff --git a/src/main/java/org/springframework/retry/annotation/CircuitBreaker.java b/src/main/java/org/springframework/retry/annotation/CircuitBreaker.java index 6cdba24..a45f10f 100644 --- a/src/main/java/org/springframework/retry/annotation/CircuitBreaker.java +++ b/src/main/java/org/springframework/retry/annotation/CircuitBreaker.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 the original author or authors. + * Copyright 2016-2024 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. @@ -181,4 +181,13 @@ public @interface CircuitBreaker { @AliasFor(annotation = Retryable.class) String exceptionExpression() default ""; + /** + * Set to {@code true} to not wrap last exception to the + * {@link org.springframework.retry.ExhaustedRetryException} when retry is exhausted. + * @return the boolean flag to whether wrap the last exception to the + * {@link org.springframework.retry.ExhaustedRetryException} + * @since 2.0.6 + */ + boolean throwLastExceptionOnExhausted() default false; + } diff --git a/src/main/java/org/springframework/retry/policy/ExpressionRetryPolicy.java b/src/main/java/org/springframework/retry/policy/ExpressionRetryPolicy.java index 914e854..093da6c 100644 --- a/src/main/java/org/springframework/retry/policy/ExpressionRetryPolicy.java +++ b/src/main/java/org/springframework/retry/policy/ExpressionRetryPolicy.java @@ -115,8 +115,8 @@ public class ExpressionRetryPolicy extends SimpleRetryPolicy implements BeanFact return super.canRetry(context); } else { - return super.canRetry(context) - && Boolean.TRUE.equals(this.expression.getValue(this.evaluationContext, lastThrowable, Boolean.class)); + return super.canRetry(context) && Boolean.TRUE + .equals(this.expression.getValue(this.evaluationContext, lastThrowable, Boolean.class)); } } diff --git a/src/test/java/org/springframework/retry/annotation/CircuitBreakerTests.java b/src/test/java/org/springframework/retry/annotation/CircuitBreakerTests.java index b6236ce..f224bbf 100644 --- a/src/test/java/org/springframework/retry/annotation/CircuitBreakerTests.java +++ b/src/test/java/org/springframework/retry/annotation/CircuitBreakerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -29,6 +29,7 @@ import org.springframework.beans.DirectFieldAccessor; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.retry.ExhaustedRetryException; import org.springframework.retry.RetryContext; import org.springframework.retry.policy.CircuitBreakerRetryPolicy; import org.springframework.retry.support.RetrySynchronizationManager; @@ -106,15 +107,21 @@ public class CircuitBreakerTests { assertThat(maxAttempts.get()).isEqualTo(10); CircuitBreakerRetryPolicy policy = TestUtils.getPropertyValue(interceptor, "retryOperations.retryPolicy", CircuitBreakerRetryPolicy.class); - Supplier openTO = TestUtils.getPropertyValue(policy, "openTimeoutSupplier", Supplier.class); + Supplier openTO = TestUtils.getPropertyValue(policy, "openTimeoutSupplier", Supplier.class); assertThat(openTO).isNotNull(); assertThat(openTO.get()).isEqualTo(10000L); - Supplier resetTO = TestUtils.getPropertyValue(policy, "resetTimeoutSupplier", Supplier.class); + Supplier resetTO = TestUtils.getPropertyValue(policy, "resetTimeoutSupplier", Supplier.class); assertThat(resetTO).isNotNull(); assertThat(resetTO.get()).isEqualTo(20000L); RetryContext ctx = service.getContext(); assertThat(TestUtils.getPropertyValue(ctx, "openWindow")).isEqualTo(10000L); assertThat(TestUtils.getPropertyValue(ctx, "timeout")).isEqualTo(20000L); + + assertThatExceptionOfType(ExhaustedRetryException.class).isThrownBy(service::exhaustedRetryService); + + assertThatExceptionOfType(RuntimeException.class).isThrownBy(service::noWrapExhaustedRetryService) + .withMessage("Planned"); + context.close(); } @@ -154,6 +161,10 @@ public class CircuitBreakerTests { void expressionService3(); + void exhaustedRetryService(); + + void noWrapExhaustedRetryService(); + int getCount(); RetryContext getContext(); @@ -197,6 +208,18 @@ public class CircuitBreakerTests { this.count++; } + @Override + @CircuitBreaker + public void exhaustedRetryService() { + throw new RuntimeException("Planned"); + } + + @Override + @CircuitBreaker(throwLastExceptionOnExhausted = true) + public void noWrapExhaustedRetryService() { + throw new RuntimeException("Planned"); + } + @Override public RetryContext getContext() { return this.context;