Align default order between @EnableAsync and @EnableResilientMethods

Retries async methods with Future return types in non-reactive path.

Closes gh-35643
This commit is contained in:
Juergen Hoeller
2025-10-15 13:52:44 +02:00
parent f15c12a190
commit 7dc78a4318
3 changed files with 47 additions and 9 deletions
@@ -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<Void> retryOperation() {
throw new IllegalStateException(Integer.toString(counter.incrementAndGet()));
}
}
@EnableResilientMethods
static class EnablingConfig {
}
@EnableAsync
@EnableResilientMethods
static class EnablingConfigWithAsync {
}
}