Browse Source

Align default order between @EnableAsync and @EnableResilientMethods

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

Closes gh-35643
pull/35649/head
Juergen Hoeller 2 months ago
parent
commit
7dc78a4318
  1. 8
      spring-context/src/main/java/org/springframework/resilience/annotation/EnableResilientMethods.java
  2. 3
      spring-context/src/main/java/org/springframework/resilience/retry/AbstractRetryInterceptor.java
  3. 45
      spring-context/src/test/java/org/springframework/resilience/RetryInterceptorTests.java

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

@ -60,10 +60,10 @@ public @interface EnableResilientMethods { @@ -60,10 +60,10 @@ public @interface EnableResilientMethods {
/**
* Indicate the order in which the {@link RetryAnnotationBeanPostProcessor}
* and {@link ConcurrencyLimitBeanPostProcessor} should be applied.
* <p>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.
* <p>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;
}

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

@ -17,6 +17,7 @@ @@ -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 { @@ -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();

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

@ -22,6 +22,8 @@ import java.nio.charset.MalformedInputException; @@ -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 @@ -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 { @@ -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 { @@ -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 { @@ -392,7 +411,7 @@ class RetryInterceptorTests {
}
static class DoubleAnnotatedBean {
static class ConcurrencyLimitAnnotatedBean {
AtomicInteger current = new AtomicInteger();
@ -419,8 +438,26 @@ class RetryInterceptorTests { @@ -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 {
}
}

Loading…
Cancel
Save