diff --git a/README.md b/README.md index 6cd78528..d1c35af2 100644 --- a/README.md +++ b/README.md @@ -184,6 +184,8 @@ By default, the context is stored in a `ThreadLocal`. JEP 444 recommends that `ThreadLocal` should be avoided when using virtual threads, available in Java 21 and beyond. To store the contexts in a `Map` instead of a `ThreadLocal`, call `RetrySynchronizationManager.setUseThreadLocal(false)`. +Also, the `RetryOperationsInterceptor` exposes `RetryOperationsInterceptor.METHOD` and `RetryOperationsInterceptor.METHOD_ARGS` attributes with `MethodInvocation.getMethod()` and `new Args(invocation.getArguments())` values, respectively, into the `RetryContext`. + ### Using `RecoveryCallback` When a retry is exhausted, the `RetryOperations` can pass control to a different @@ -572,8 +574,8 @@ class Service { } ``` -Version 1.2 introduced the ability to use expressions for certain properties. The -following example show how to use expressions this way: +Version 1.2 introduced the ability to use expressions for certain properties. +The following example show how to use expressions this way: ```java @@ -595,17 +597,12 @@ public void service3() { } ``` -Since Spring Retry 1.2.5, for `exceptionExpression`, templated expressions (`#{...}`) are -deprecated in favor of simple expression strings -(`message.contains('this can be retried')`). +Since Spring Retry 1.2.5, for `exceptionExpression`, templated expressions (`#{...}`) are deprecated in favor of simple expression strings (`message.contains('this can be retried')`). -Expressions can contain property placeholders, such as `#{${max.delay}}` or -`#{@exceptionChecker.${retry.method}(#root)}`. The following rules apply: +Expressions can contain property placeholders, such as `#{${max.delay}}` or `#{@exceptionChecker.${retry.method}(#root)}`. The following rules apply: - `exceptionExpression` is evaluated against the thrown exception as the `#root` object. -- `maxAttemptsExpression` and the `@BackOff` expression attributes are evaluated once, -during initialization. There is no root object for the evaluation but they can reference -other beans in the context +- `maxAttemptsExpression` and the `@BackOff` expression attributes are evaluated once, during initialization. There is no root object for the evaluation, but they can reference other beans in the context Starting with version 2.0, expressions in `@Retryable`, `@CircuitBreaker`, and `BackOff` can be evaluated once, during application initialization, or at runtime. With earlier versions, evaluation was always performed during initialization (except for `Retryable.exceptionExpression` which is always evaluated at runtime). diff --git a/src/main/java/org/springframework/retry/interceptor/RetryOperationsInterceptor.java b/src/main/java/org/springframework/retry/interceptor/RetryOperationsInterceptor.java index 2fa20977..a8eba86d 100644 --- a/src/main/java/org/springframework/retry/interceptor/RetryOperationsInterceptor.java +++ b/src/main/java/org/springframework/retry/interceptor/RetryOperationsInterceptor.java @@ -29,7 +29,6 @@ import org.springframework.retry.support.RetrySynchronizationManager; import org.springframework.retry.support.RetryTemplate; import org.springframework.util.Assert; -import org.springframework.util.StringUtils; /** * A {@link MethodInterceptor} that can be used to automatically retry calls to a method @@ -43,6 +42,13 @@ * intercepted is also transactional, then use the ordering hints in the advice * declarations to ensure that this one is before the transaction interceptor in the * advice chain. + *
+ * An internal {@link MethodInvocationRetryCallback} implementation exposes a
+ * {@value RetryOperationsInterceptor#METHOD} attribute into the provided
+ * {@link RetryContext} with a value from {@link MethodInvocation#getMethod()}. In
+ * addition, the arguments of this method are exposed into a
+ * {@value RetryOperationsInterceptor#METHOD_ARGS} attribute as an {@link Args} instance
+ * wrapper.
*
* @author Rob Harrop
* @author Dave Syer
@@ -50,6 +56,18 @@
*/
public class RetryOperationsInterceptor implements MethodInterceptor {
+ /**
+ * The {@link RetryContext} attribute name for the
+ * {@link MethodInvocation#getMethod()}.
+ */
+ public static final String METHOD = "method";
+
+ /**
+ * The {@link RetryContext} attribute name for the
+ * {@code new Args(invocation.getArguments())}.
+ */
+ public static final String METHOD_ARGS = "methodArgs";
+
private RetryOperations retryOperations = new RetryTemplate();
@Nullable
@@ -78,7 +96,11 @@ public Object invoke(final MethodInvocation invocation) throws Throwable {
public Object doWithRetry(RetryContext context) throws Exception {
context.setAttribute(RetryContext.NAME, this.label);
- context.setAttribute("ARGS", new Args(invocation.getArguments()));
+ Args args = new Args(invocation.getArguments());
+ context.setAttribute(METHOD, invocation.getMethod());
+ context.setAttribute(METHOD_ARGS, args);
+ // TODO remove this attribute in the next major/minor version
+ context.setAttribute("ARGS", args);
/*
* If we don't copy the invocation carefully it won't keep a reference to
diff --git a/src/test/java/org/springframework/retry/interceptor/RetryOperationsInterceptorTests.java b/src/test/java/org/springframework/retry/interceptor/RetryOperationsInterceptorTests.java
index da688a2d..887d9027 100644
--- a/src/test/java/org/springframework/retry/interceptor/RetryOperationsInterceptorTests.java
+++ b/src/test/java/org/springframework/retry/interceptor/RetryOperationsInterceptorTests.java
@@ -40,6 +40,7 @@
import org.springframework.retry.policy.NeverRetryPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
+import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.ClassUtils;
@@ -53,6 +54,7 @@
* @author Gary Russell
* @author Stéphane Nicoll
* @author Henning Pöttker
+ * @author Artem Bilan
*/
public class RetryOperationsInterceptorTests {
@@ -121,6 +123,12 @@ public void testDefaultInterceptorWithLabel() throws Exception {
this.service.service();
assertThat(count).isEqualTo(2);
assertThat(this.context.getAttribute(RetryContext.NAME)).isEqualTo("FOO");
+ assertThat(this.context.getAttribute(RetryOperationsInterceptor.METHOD)).isNotNull()
+ .extracting("name")
+ .isEqualTo("service");
+ assertThat(this.context.getAttribute(RetryOperationsInterceptor.METHOD_ARGS)).isNotNull()
+ .extracting("args")
+ .isEqualTo(new Object[0]);
}
@Test
@@ -206,13 +214,13 @@ public void testRetryExceptionAfterTooManyAttempts() {
}
@Test
- public void testOutsideTransaction() throws Exception {
+ public void testOutsideTransaction() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
ClassUtils.addResourcePathToPackagePath(getClass(), "retry-transaction-test.xml"));
Object object = context.getBean("bean");
assertThat(object).isInstanceOf(Service.class);
Service bean = (Service) object;
- bean.doTansactional();
+ bean.doTransactional();
assertThat(count).isEqualTo(2);
// Expect 2 separate transactions...
assertThat(transactionCount).isEqualTo(2);
@@ -220,7 +228,7 @@ public void testOutsideTransaction() throws Exception {
}
@Test
- public void testIllegalMethodInvocationType() throws Throwable {
+ public void testIllegalMethodInvocationType() {
assertThatIllegalStateException().isThrownBy(() -> this.interceptor.invoke(new MethodInvocation() {
@Override
public Method getMethod() {
@@ -253,7 +261,7 @@ public static interface Service {
void service() throws Exception;
- void doTansactional();
+ void doTransactional();
}
@@ -269,18 +277,18 @@ public void service() throws Exception {
}
}
- @SuppressWarnings("deprecation")
@Override
- public void doTansactional() {
+ public void doTransactional() {
if (TransactionSynchronizationManager.isActualTransactionActive() && !this.enteredTransaction) {
transactionCount++;
- TransactionSynchronizationManager.registerSynchronization(
- new org.springframework.transaction.support.TransactionSynchronizationAdapter() {
- @Override
- public void beforeCompletion() {
- ServiceImpl.this.enteredTransaction = false;
- }
- });
+ TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
+
+ @Override
+ public void beforeCompletion() {
+ ServiceImpl.this.enteredTransaction = false;
+ }
+
+ });
this.enteredTransaction = true;
}
count++;
diff --git a/src/test/resources/org/springframework/retry/interceptor/retry-transaction-test.xml b/src/test/resources/org/springframework/retry/interceptor/retry-transaction-test.xml
index 83788d94..a0cd7c26 100644
--- a/src/test/resources/org/springframework/retry/interceptor/retry-transaction-test.xml
+++ b/src/test/resources/org/springframework/retry/interceptor/retry-transaction-test.xml
@@ -2,16 +2,15 @@