diff --git a/spring-context/src/main/java/org/springframework/resilience/annotation/EnableResilientMethods.java b/spring-context/src/main/java/org/springframework/resilience/annotation/EnableResilientMethods.java index 4facad6d4e3..e8c00bfc887 100644 --- a/spring-context/src/main/java/org/springframework/resilience/annotation/EnableResilientMethods.java +++ b/spring-context/src/main/java/org/springframework/resilience/annotation/EnableResilientMethods.java @@ -60,10 +60,10 @@ public @interface EnableResilientMethods { /** * Indicate the order in which the {@link RetryAnnotationBeanPostProcessor} * and {@link ConcurrencyLimitBeanPostProcessor} should be applied. - *
The default is {@link Ordered#LOWEST_PRECEDENCE} in order to run - * after all other post-processors, so that they can add advisors to - * existing proxies rather than double-proxy. + *
The default is {@link Ordered#LOWEST_PRECEDENCE - 1} in order to run
+ * after all common post-processors, except for {@code @EnableAsync}.
+ * @see org.springframework.scheduling.annotation.EnableAsync#order()
*/
- int order() default Ordered.LOWEST_PRECEDENCE;
+ int order() default Ordered.LOWEST_PRECEDENCE - 1;
}
diff --git a/spring-context/src/main/java/org/springframework/resilience/retry/AbstractRetryInterceptor.java b/spring-context/src/main/java/org/springframework/resilience/retry/AbstractRetryInterceptor.java
index b85a181fc99..d4a14fa3427 100644
--- a/spring-context/src/main/java/org/springframework/resilience/retry/AbstractRetryInterceptor.java
+++ b/spring-context/src/main/java/org/springframework/resilience/retry/AbstractRetryInterceptor.java
@@ -17,6 +17,7 @@
package org.springframework.resilience.retry;
import java.lang.reflect.Method;
+import java.util.concurrent.Future;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
@@ -77,7 +78,7 @@ public abstract class AbstractRetryInterceptor implements MethodInterceptor {
return invocation.proceed();
}
- if (this.reactiveAdapterRegistry != null) {
+ if (this.reactiveAdapterRegistry != null && !Future.class.isAssignableFrom(method.getReturnType())) {
ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(method.getReturnType());
if (adapter != null) {
Object result = invocation.proceed();
diff --git a/spring-context/src/test/java/org/springframework/resilience/RetryInterceptorTests.java b/spring-context/src/test/java/org/springframework/resilience/RetryInterceptorTests.java
index 456687a7579..7e32ebdaa59 100644
--- a/spring-context/src/test/java/org/springframework/resilience/RetryInterceptorTests.java
+++ b/spring-context/src/test/java/org/springframework/resilience/RetryInterceptorTests.java
@@ -22,6 +22,8 @@ import java.nio.charset.MalformedInputException;
import java.nio.file.AccessDeniedException;
import java.time.Duration;
import java.util.Properties;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
import java.util.concurrent.atomic.AtomicInteger;
import org.aopalliance.intercept.MethodInterceptor;
@@ -44,8 +46,11 @@ import org.springframework.resilience.annotation.RetryAnnotationBeanPostProcesso
import org.springframework.resilience.annotation.Retryable;
import org.springframework.resilience.retry.MethodRetrySpec;
import org.springframework.resilience.retry.SimpleRetryInterceptor;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.scheduling.annotation.EnableAsync;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIOException;
import static org.assertj.core.api.Assertions.assertThatRuntimeException;
@@ -265,11 +270,11 @@ class RetryInterceptorTests {
@Test
void withEnableAnnotation() throws Exception {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
- ctx.registerBeanDefinition("bean", new RootBeanDefinition(DoubleAnnotatedBean.class));
+ ctx.registerBeanDefinition("bean", new RootBeanDefinition(ConcurrencyLimitAnnotatedBean.class));
ctx.registerBeanDefinition("config", new RootBeanDefinition(EnablingConfig.class));
ctx.refresh();
- DoubleAnnotatedBean proxy = ctx.getBean(DoubleAnnotatedBean.class);
- DoubleAnnotatedBean target = (DoubleAnnotatedBean) AopProxyUtils.getSingletonTarget(proxy);
+ ConcurrencyLimitAnnotatedBean proxy = ctx.getBean(ConcurrencyLimitAnnotatedBean.class);
+ ConcurrencyLimitAnnotatedBean target = (ConcurrencyLimitAnnotatedBean) AopProxyUtils.getSingletonTarget(proxy);
Thread thread = new Thread(() -> assertThatIOException().isThrownBy(proxy::retryOperation));
thread.start();
@@ -279,6 +284,20 @@ class RetryInterceptorTests {
assertThat(target.threadChange).hasValue(2);
}
+ @Test
+ void withAsyncAnnotation() {
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
+ ctx.registerBeanDefinition("bean", new RootBeanDefinition(AsyncAnnotatedBean.class));
+ ctx.registerBeanDefinition("config", new RootBeanDefinition(EnablingConfigWithAsync.class));
+ ctx.refresh();
+ AsyncAnnotatedBean proxy = ctx.getBean(AsyncAnnotatedBean.class);
+ AsyncAnnotatedBean target = (AsyncAnnotatedBean) AopProxyUtils.getSingletonTarget(proxy);
+
+ assertThatExceptionOfType(CompletionException.class).isThrownBy(() -> proxy.retryOperation().join())
+ .withCauseInstanceOf(IllegalStateException.class);
+ assertThat(target.counter).hasValue(3);
+ }
+
static class NonAnnotatedBean implements PlainInterface {
@@ -392,7 +411,7 @@ class RetryInterceptorTests {
}
- static class DoubleAnnotatedBean {
+ static class ConcurrencyLimitAnnotatedBean {
AtomicInteger current = new AtomicInteger();
@@ -419,8 +438,26 @@ class RetryInterceptorTests {
}
+ static class AsyncAnnotatedBean {
+
+ AtomicInteger counter = new AtomicInteger();
+
+ @Async
+ @Retryable(maxAttempts = 2, delay = 10)
+ public CompletableFuture