Skip to content

Commit

Permalink
GH-229: Expose method & methodArgs into ctx from `RetryOperations…
Browse files Browse the repository at this point in the history
…Interceptor`

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
  • Loading branch information
artembilan committed Sep 13, 2024
1 parent 5493ab7 commit dfc95fe
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 31 deletions.
17 changes: 7 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -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).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -43,13 +42,32 @@
* 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.
* <p>
* 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
* @author Artem Bilan
*/
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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -53,6 +54,7 @@
* @author Gary Russell
* @author Stéphane Nicoll
* @author Henning Pöttker
* @author Artem Bilan
*/
public class RetryOperationsInterceptorTests {

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -206,21 +214,21 @@ 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);
context.close();
}

@Test
public void testIllegalMethodInvocationType() throws Throwable {
public void testIllegalMethodInvocationType() {
assertThatIllegalStateException().isThrownBy(() -> this.interceptor.invoke(new MethodInvocation() {
@Override
public Method getMethod() {
Expand Down Expand Up @@ -253,7 +261,7 @@ public static interface Service {

void service() throws Exception;

void doTansactional();
void doTransactional();

}

Expand All @@ -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++;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,15 @@
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd">

<aop:config>
<aop:pointcut id="transactional"
expression="execution(* org.springframework..RetryOperationsInterceptorTests.Service.doTansactional(..))" />
expression="execution(* org.springframework..RetryOperationsInterceptorTests.Service.doTransactional(..))" />
<aop:advisor pointcut-ref="transactional"
advice-ref="retryAdvice" order="-1"/>
<aop:advisor pointcut-ref="transactional" advice-ref="txAdvice" order="0"/>
Expand All @@ -23,7 +22,7 @@
<bean id="retryAdvice"
class="org.springframework.retry.interceptor.RetryOperationsInterceptor"/>

<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="*" />
</tx:attributes>
Expand Down

0 comments on commit dfc95fe

Please sign in to comment.