GH-82: Expose @CircuitBreaker.throwLastExceptionOnExhausted()
Fixes: #82 There are some use-cases when `ExhaustedRetryException` does not fit into the logic around Circuit Breaker pattern. The `RetryTemplate` has already a `throwLastExceptionOnExhausted` flag for stateful retries * Expose `@CircuitBreaker.throwLastExceptionOnExhausted()` and propagate it down to the `RetryTemplate` in the `AnnotationAwareRetryOperationsInterceptor`
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user