Skip to content

Commit dfc95fe

Browse files
committed
GH-229: Expose method & methodArgs into ctx from RetryOperationsInterceptor
Fixes: #229 Issue link: #229 The logic in the target `RetryPolicy` might be based on the method and its arguments we retry. * Expose `method` & `methodArgs` `RetryContext` attributes from an internal implementation of the `MethodInvocationRetryCallback` in the `RetryOperationsInterceptor` * Document these attributes
1 parent 5493ab7 commit dfc95fe

File tree

4 files changed

+57
-31
lines changed

4 files changed

+57
-31
lines changed

README.md

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,8 @@ By default, the context is stored in a `ThreadLocal`.
184184
JEP 444 recommends that `ThreadLocal` should be avoided when using virtual threads, available in Java 21 and beyond.
185185
To store the contexts in a `Map` instead of a `ThreadLocal`, call `RetrySynchronizationManager.setUseThreadLocal(false)`.
186186

187+
Also, the `RetryOperationsInterceptor` exposes `RetryOperationsInterceptor.METHOD` and `RetryOperationsInterceptor.METHOD_ARGS` attributes with `MethodInvocation.getMethod()` and `new Args(invocation.getArguments())` values, respectively, into the `RetryContext`.
188+
187189
### Using `RecoveryCallback`
188190

189191
When a retry is exhausted, the `RetryOperations` can pass control to a different
@@ -572,8 +574,8 @@ class Service {
572574
}
573575
```
574576

575-
Version 1.2 introduced the ability to use expressions for certain properties. The
576-
following example show how to use expressions this way:
577+
Version 1.2 introduced the ability to use expressions for certain properties.
578+
The following example show how to use expressions this way:
577579

578580
```java
579581

@@ -595,17 +597,12 @@ public void service3() {
595597
}
596598
```
597599

598-
Since Spring Retry 1.2.5, for `exceptionExpression`, templated expressions (`#{...}`) are
599-
deprecated in favor of simple expression strings
600-
(`message.contains('this can be retried')`).
600+
Since Spring Retry 1.2.5, for `exceptionExpression`, templated expressions (`#{...}`) are deprecated in favor of simple expression strings (`message.contains('this can be retried')`).
601601

602-
Expressions can contain property placeholders, such as `#{${max.delay}}` or
603-
`#{@exceptionChecker.${retry.method}(#root)}`. The following rules apply:
602+
Expressions can contain property placeholders, such as `#{${max.delay}}` or `#{@exceptionChecker.${retry.method}(#root)}`. The following rules apply:
604603

605604
- `exceptionExpression` is evaluated against the thrown exception as the `#root` object.
606-
- `maxAttemptsExpression` and the `@BackOff` expression attributes are evaluated once,
607-
during initialization. There is no root object for the evaluation but they can reference
608-
other beans in the context
605+
- `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
609606

610607
Starting with version 2.0, expressions in `@Retryable`, `@CircuitBreaker`, and `BackOff` can be evaluated once, during application initialization, or at runtime.
611608
With earlier versions, evaluation was always performed during initialization (except for `Retryable.exceptionExpression` which is always evaluated at runtime).

src/main/java/org/springframework/retry/interceptor/RetryOperationsInterceptor.java

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
import org.springframework.retry.support.RetrySynchronizationManager;
3030
import org.springframework.retry.support.RetryTemplate;
3131
import org.springframework.util.Assert;
32-
import org.springframework.util.StringUtils;
3332

3433
/**
3534
* A {@link MethodInterceptor} that can be used to automatically retry calls to a method
@@ -43,13 +42,32 @@
4342
* intercepted is also transactional, then use the ordering hints in the advice
4443
* declarations to ensure that this one is before the transaction interceptor in the
4544
* advice chain.
45+
* <p>
46+
* An internal {@link MethodInvocationRetryCallback} implementation exposes a
47+
* {@value RetryOperationsInterceptor#METHOD} attribute into the provided
48+
* {@link RetryContext} with a value from {@link MethodInvocation#getMethod()}. In
49+
* addition, the arguments of this method are exposed into a
50+
* {@value RetryOperationsInterceptor#METHOD_ARGS} attribute as an {@link Args} instance
51+
* wrapper.
4652
*
4753
* @author Rob Harrop
4854
* @author Dave Syer
4955
* @author Artem Bilan
5056
*/
5157
public class RetryOperationsInterceptor implements MethodInterceptor {
5258

59+
/**
60+
* The {@link RetryContext} attribute name for the
61+
* {@link MethodInvocation#getMethod()}.
62+
*/
63+
public static final String METHOD = "method";
64+
65+
/**
66+
* The {@link RetryContext} attribute name for the
67+
* {@code new Args(invocation.getArguments())}.
68+
*/
69+
public static final String METHOD_ARGS = "methodArgs";
70+
5371
private RetryOperations retryOperations = new RetryTemplate();
5472

5573
@Nullable
@@ -78,7 +96,11 @@ public Object invoke(final MethodInvocation invocation) throws Throwable {
7896
public Object doWithRetry(RetryContext context) throws Exception {
7997

8098
context.setAttribute(RetryContext.NAME, this.label);
81-
context.setAttribute("ARGS", new Args(invocation.getArguments()));
99+
Args args = new Args(invocation.getArguments());
100+
context.setAttribute(METHOD, invocation.getMethod());
101+
context.setAttribute(METHOD_ARGS, args);
102+
// TODO remove this attribute in the next major/minor version
103+
context.setAttribute("ARGS", args);
82104

83105
/*
84106
* If we don't copy the invocation carefully it won't keep a reference to

src/test/java/org/springframework/retry/interceptor/RetryOperationsInterceptorTests.java

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import org.springframework.retry.policy.NeverRetryPolicy;
4141
import org.springframework.retry.policy.SimpleRetryPolicy;
4242
import org.springframework.retry.support.RetryTemplate;
43+
import org.springframework.transaction.support.TransactionSynchronization;
4344
import org.springframework.transaction.support.TransactionSynchronizationManager;
4445
import org.springframework.util.ClassUtils;
4546

@@ -53,6 +54,7 @@
5354
* @author Gary Russell
5455
* @author Stéphane Nicoll
5556
* @author Henning Pöttker
57+
* @author Artem Bilan
5658
*/
5759
public class RetryOperationsInterceptorTests {
5860

@@ -121,6 +123,12 @@ public void testDefaultInterceptorWithLabel() throws Exception {
121123
this.service.service();
122124
assertThat(count).isEqualTo(2);
123125
assertThat(this.context.getAttribute(RetryContext.NAME)).isEqualTo("FOO");
126+
assertThat(this.context.getAttribute(RetryOperationsInterceptor.METHOD)).isNotNull()
127+
.extracting("name")
128+
.isEqualTo("service");
129+
assertThat(this.context.getAttribute(RetryOperationsInterceptor.METHOD_ARGS)).isNotNull()
130+
.extracting("args")
131+
.isEqualTo(new Object[0]);
124132
}
125133

126134
@Test
@@ -206,21 +214,21 @@ public void testRetryExceptionAfterTooManyAttempts() {
206214
}
207215

208216
@Test
209-
public void testOutsideTransaction() throws Exception {
217+
public void testOutsideTransaction() {
210218
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
211219
ClassUtils.addResourcePathToPackagePath(getClass(), "retry-transaction-test.xml"));
212220
Object object = context.getBean("bean");
213221
assertThat(object).isInstanceOf(Service.class);
214222
Service bean = (Service) object;
215-
bean.doTansactional();
223+
bean.doTransactional();
216224
assertThat(count).isEqualTo(2);
217225
// Expect 2 separate transactions...
218226
assertThat(transactionCount).isEqualTo(2);
219227
context.close();
220228
}
221229

222230
@Test
223-
public void testIllegalMethodInvocationType() throws Throwable {
231+
public void testIllegalMethodInvocationType() {
224232
assertThatIllegalStateException().isThrownBy(() -> this.interceptor.invoke(new MethodInvocation() {
225233
@Override
226234
public Method getMethod() {
@@ -253,7 +261,7 @@ public static interface Service {
253261

254262
void service() throws Exception;
255263

256-
void doTansactional();
264+
void doTransactional();
257265

258266
}
259267

@@ -269,18 +277,18 @@ public void service() throws Exception {
269277
}
270278
}
271279

272-
@SuppressWarnings("deprecation")
273280
@Override
274-
public void doTansactional() {
281+
public void doTransactional() {
275282
if (TransactionSynchronizationManager.isActualTransactionActive() && !this.enteredTransaction) {
276283
transactionCount++;
277-
TransactionSynchronizationManager.registerSynchronization(
278-
new org.springframework.transaction.support.TransactionSynchronizationAdapter() {
279-
@Override
280-
public void beforeCompletion() {
281-
ServiceImpl.this.enteredTransaction = false;
282-
}
283-
});
284+
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
285+
286+
@Override
287+
public void beforeCompletion() {
288+
ServiceImpl.this.enteredTransaction = false;
289+
}
290+
291+
});
284292
this.enteredTransaction = true;
285293
}
286294
count++;

src/test/resources/org/springframework/retry/interceptor/retry-transaction-test.xml

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,15 @@
22
<beans xmlns="http://www.springframework.org/schema/beans"
33
xmlns:aop="http://www.springframework.org/schema/aop"
44
xmlns:tx="http://www.springframework.org/schema/tx"
5-
xmlns:p="http://www.springframework.org/schema/p"
65
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
76
xsi:schemaLocation="
8-
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-2.0.xsd
9-
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop-2.0.xsd
10-
http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
7+
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
8+
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd
9+
http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd">
1110

1211
<aop:config>
1312
<aop:pointcut id="transactional"
14-
expression="execution(* org.springframework..RetryOperationsInterceptorTests.Service.doTansactional(..))" />
13+
expression="execution(* org.springframework..RetryOperationsInterceptorTests.Service.doTransactional(..))" />
1514
<aop:advisor pointcut-ref="transactional"
1615
advice-ref="retryAdvice" order="-1"/>
1716
<aop:advisor pointcut-ref="transactional" advice-ref="txAdvice" order="0"/>
@@ -23,7 +22,7 @@
2322
<bean id="retryAdvice"
2423
class="org.springframework.retry.interceptor.RetryOperationsInterceptor"/>
2524

26-
<tx:advice id="txAdvice" transaction-manager="transactionManager">
25+
<tx:advice id="txAdvice">
2726
<tx:attributes>
2827
<tx:method name="*" />
2928
</tx:attributes>

0 commit comments

Comments
 (0)