Skip to content

Commit 7dc78a4

Browse files
committed
Align default order between @EnableAsync and @EnableResilientMethods
Retries async methods with Future return types in non-reactive path. Closes gh-35643
1 parent f15c12a commit 7dc78a4

File tree

3 files changed

+47
-9
lines changed

3 files changed

+47
-9
lines changed

spring-context/src/main/java/org/springframework/resilience/annotation/EnableResilientMethods.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,10 @@
6060
/**
6161
* Indicate the order in which the {@link RetryAnnotationBeanPostProcessor}
6262
* and {@link ConcurrencyLimitBeanPostProcessor} should be applied.
63-
* <p>The default is {@link Ordered#LOWEST_PRECEDENCE} in order to run
64-
* after all other post-processors, so that they can add advisors to
65-
* existing proxies rather than double-proxy.
63+
* <p>The default is {@link Ordered#LOWEST_PRECEDENCE - 1} in order to run
64+
* after all common post-processors, except for {@code @EnableAsync}.
65+
* @see org.springframework.scheduling.annotation.EnableAsync#order()
6666
*/
67-
int order() default Ordered.LOWEST_PRECEDENCE;
67+
int order() default Ordered.LOWEST_PRECEDENCE - 1;
6868

6969
}

spring-context/src/main/java/org/springframework/resilience/retry/AbstractRetryInterceptor.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.resilience.retry;
1818

1919
import java.lang.reflect.Method;
20+
import java.util.concurrent.Future;
2021

2122
import org.aopalliance.intercept.MethodInterceptor;
2223
import org.aopalliance.intercept.MethodInvocation;
@@ -77,7 +78,7 @@ public AbstractRetryInterceptor() {
7778
return invocation.proceed();
7879
}
7980

80-
if (this.reactiveAdapterRegistry != null) {
81+
if (this.reactiveAdapterRegistry != null && !Future.class.isAssignableFrom(method.getReturnType())) {
8182
ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(method.getReturnType());
8283
if (adapter != null) {
8384
Object result = invocation.proceed();

spring-context/src/test/java/org/springframework/resilience/RetryInterceptorTests.java

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import java.nio.file.AccessDeniedException;
2323
import java.time.Duration;
2424
import java.util.Properties;
25+
import java.util.concurrent.CompletableFuture;
26+
import java.util.concurrent.CompletionException;
2527
import java.util.concurrent.atomic.AtomicInteger;
2628

2729
import org.aopalliance.intercept.MethodInterceptor;
@@ -44,8 +46,11 @@
4446
import org.springframework.resilience.annotation.Retryable;
4547
import org.springframework.resilience.retry.MethodRetrySpec;
4648
import org.springframework.resilience.retry.SimpleRetryInterceptor;
49+
import org.springframework.scheduling.annotation.Async;
50+
import org.springframework.scheduling.annotation.EnableAsync;
4751

4852
import static org.assertj.core.api.Assertions.assertThat;
53+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
4954
import static org.assertj.core.api.Assertions.assertThatIOException;
5055
import static org.assertj.core.api.Assertions.assertThatRuntimeException;
5156

@@ -265,11 +270,11 @@ void withPostProcessorForClassWithZeroAttempts() {
265270
@Test
266271
void withEnableAnnotation() throws Exception {
267272
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
268-
ctx.registerBeanDefinition("bean", new RootBeanDefinition(DoubleAnnotatedBean.class));
273+
ctx.registerBeanDefinition("bean", new RootBeanDefinition(ConcurrencyLimitAnnotatedBean.class));
269274
ctx.registerBeanDefinition("config", new RootBeanDefinition(EnablingConfig.class));
270275
ctx.refresh();
271-
DoubleAnnotatedBean proxy = ctx.getBean(DoubleAnnotatedBean.class);
272-
DoubleAnnotatedBean target = (DoubleAnnotatedBean) AopProxyUtils.getSingletonTarget(proxy);
276+
ConcurrencyLimitAnnotatedBean proxy = ctx.getBean(ConcurrencyLimitAnnotatedBean.class);
277+
ConcurrencyLimitAnnotatedBean target = (ConcurrencyLimitAnnotatedBean) AopProxyUtils.getSingletonTarget(proxy);
273278

274279
Thread thread = new Thread(() -> assertThatIOException().isThrownBy(proxy::retryOperation));
275280
thread.start();
@@ -279,6 +284,20 @@ void withEnableAnnotation() throws Exception {
279284
assertThat(target.threadChange).hasValue(2);
280285
}
281286

287+
@Test
288+
void withAsyncAnnotation() {
289+
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
290+
ctx.registerBeanDefinition("bean", new RootBeanDefinition(AsyncAnnotatedBean.class));
291+
ctx.registerBeanDefinition("config", new RootBeanDefinition(EnablingConfigWithAsync.class));
292+
ctx.refresh();
293+
AsyncAnnotatedBean proxy = ctx.getBean(AsyncAnnotatedBean.class);
294+
AsyncAnnotatedBean target = (AsyncAnnotatedBean) AopProxyUtils.getSingletonTarget(proxy);
295+
296+
assertThatExceptionOfType(CompletionException.class).isThrownBy(() -> proxy.retryOperation().join())
297+
.withCauseInstanceOf(IllegalStateException.class);
298+
assertThat(target.counter).hasValue(3);
299+
}
300+
282301

283302
static class NonAnnotatedBean implements PlainInterface {
284303

@@ -392,7 +411,7 @@ public void overrideOperation() throws IOException {
392411
}
393412

394413

395-
static class DoubleAnnotatedBean {
414+
static class ConcurrencyLimitAnnotatedBean {
396415

397416
AtomicInteger current = new AtomicInteger();
398417

@@ -419,8 +438,26 @@ public void retryOperation() throws IOException, InterruptedException {
419438
}
420439

421440

441+
static class AsyncAnnotatedBean {
442+
443+
AtomicInteger counter = new AtomicInteger();
444+
445+
@Async
446+
@Retryable(maxAttempts = 2, delay = 10)
447+
public CompletableFuture<Void> retryOperation() {
448+
throw new IllegalStateException(Integer.toString(counter.incrementAndGet()));
449+
}
450+
}
451+
452+
422453
@EnableResilientMethods
423454
static class EnablingConfig {
424455
}
425456

457+
458+
@EnableAsync
459+
@EnableResilientMethods
460+
static class EnablingConfigWithAsync {
461+
}
462+
426463
}

0 commit comments

Comments
 (0)