Skip to content

Commit e19f5d7

Browse files
committed
GH-422: Log label in RetryTemplate
Fixes: #422 * Push `MethodInvocationRetryCallback.getLabel()` to the `default` method in the `RetryCallback` * Use `ClassUtils.getQualifiedMethodName(invocation.getMethod())` for default label in the `MethodInvocationRetryCallback` if not provided from the outside * Refactor some logic in the `RetryOperationsInterceptor` to cover `context.removeAttribute("__proxy__")` for use-case when `recoverer` is not provided. Remove extra label resolution since it is already done in the `MethodInvocationRetryCallback` * Fix Javadoc typos in the affected classes
1 parent 6656151 commit e19f5d7

File tree

4 files changed

+52
-63
lines changed

4 files changed

+52
-63
lines changed

src/main/java/org/springframework/retry/RetryCallback.java

+11
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
* @param <E> the type of exception it declares may be thrown
2525
* @author Rob Harrop
2626
* @author Dave Syer
27+
* @author Artem Bilan
2728
*/
2829
public interface RetryCallback<T, E extends Throwable> {
2930

@@ -37,4 +38,14 @@ public interface RetryCallback<T, E extends Throwable> {
3738
*/
3839
T doWithRetry(RetryContext context) throws E;
3940

41+
/**
42+
* A logical identifier for this callback to distinguish retries around business
43+
* operations.
44+
* @return the identifier for this callback.
45+
* @since 2.0.6
46+
*/
47+
default String getLabel() {
48+
return null;
49+
}
50+
4051
}

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

+11-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2006-2019 the original author or authors.
2+
* Copyright 2006-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,14 +17,17 @@
1717
package org.springframework.retry.interceptor;
1818

1919
import org.aopalliance.intercept.MethodInvocation;
20+
21+
import org.springframework.lang.Nullable;
2022
import org.springframework.retry.RetryCallback;
2123
import org.springframework.retry.RetryOperations;
24+
import org.springframework.util.ClassUtils;
2225
import org.springframework.util.StringUtils;
2326

2427
/**
2528
* Callback class for a Spring AOP reflective `MethodInvocation` that can be retried using
2629
* a {@link RetryOperations}.
27-
*
30+
* <p>
2831
* In a concrete {@link org.springframework.retry.RetryListener} implementation, the
2932
* `MethodInvocation` can be analysed for providing insights on the method called as well
3033
* as its parameter values which could then be used for monitoring purposes.
@@ -35,6 +38,7 @@
3538
* @see RetryOperationsInterceptor
3639
* @see org.springframework.retry.listener.MethodInvocationRetryListenerSupport
3740
* @author Marius Grama
41+
* @author Artem Bilan
3842
* @since 1.3
3943
*/
4044
public abstract class MethodInvocationRetryCallback<T, E extends Throwable> implements RetryCallback<T, E> {
@@ -48,22 +52,23 @@ public abstract class MethodInvocationRetryCallback<T, E extends Throwable> impl
4852
* @param invocation the method invocation
4953
* @param label a unique label for statistics reporting.
5054
*/
51-
public MethodInvocationRetryCallback(MethodInvocation invocation, String label) {
55+
public MethodInvocationRetryCallback(MethodInvocation invocation, @Nullable String label) {
5256
this.invocation = invocation;
5357
if (StringUtils.hasText(label)) {
5458
this.label = label;
5559
}
5660
else {
57-
this.label = invocation.getMethod().toGenericString();
61+
this.label = ClassUtils.getQualifiedMethodName(invocation.getMethod());
5862
}
5963
}
6064

6165
public MethodInvocation getInvocation() {
62-
return invocation;
66+
return this.invocation;
6367
}
6468

69+
@Override
6570
public String getLabel() {
66-
return label;
71+
return this.label;
6772
}
6873

6974
}

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

+18-49
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2006-2022 the original author or authors.
2+
* Copyright 2006-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,12 +16,11 @@
1616

1717
package org.springframework.retry.interceptor;
1818

19-
import java.util.Arrays;
20-
2119
import org.aopalliance.intercept.MethodInterceptor;
2220
import org.aopalliance.intercept.MethodInvocation;
2321

2422
import org.springframework.aop.ProxyMethodInvocation;
23+
import org.springframework.lang.Nullable;
2524
import org.springframework.retry.RecoveryCallback;
2625
import org.springframework.retry.RetryCallback;
2726
import org.springframework.retry.RetryContext;
@@ -35,9 +34,9 @@
3534
/**
3635
* A {@link MethodInterceptor} that can be used to automatically retry calls to a method
3736
* on a service if it fails. The injected {@link RetryOperations} is used to control the
38-
* number of retries. By default it will retry a fixed number of times, according to the
37+
* number of retries. By default, it will retry a fixed number of times, according to the
3938
* defaults in {@link RetryTemplate}.
40-
*
39+
* <p>
4140
* Hint about transaction boundaries. If you want to retry a failed transaction you need
4241
* to make sure that the transaction boundary is inside the retry, otherwise the
4342
* successful attempt will roll back with the whole transaction. If the method being
@@ -47,11 +46,13 @@
4746
*
4847
* @author Rob Harrop
4948
* @author Dave Syer
49+
* @author Artem Bilan
5050
*/
5151
public class RetryOperationsInterceptor implements MethodInterceptor {
5252

5353
private RetryOperations retryOperations = new RetryTemplate();
5454

55+
@Nullable
5556
private MethodInvocationRecoverer<?> recoverer;
5657

5758
private String label;
@@ -71,18 +72,7 @@ public void setRecoverer(MethodInvocationRecoverer<?> recoverer) {
7172

7273
@Override
7374
public Object invoke(final MethodInvocation invocation) throws Throwable {
74-
75-
String name;
76-
if (StringUtils.hasText(this.label)) {
77-
name = this.label;
78-
}
79-
else {
80-
name = invocation.getMethod().toGenericString();
81-
}
82-
final String label = name;
83-
84-
RetryCallback<Object, Throwable> retryCallback = new MethodInvocationRetryCallback<Object, Throwable>(
85-
invocation, label) {
75+
RetryCallback<Object, Throwable> retryCallback = new MethodInvocationRetryCallback<>(invocation, this.label) {
8676

8777
@Override
8878
public Object doWithRetry(RetryContext context) throws Exception {
@@ -117,42 +107,21 @@ public Object doWithRetry(RetryContext context) throws Exception {
117107

118108
};
119109

120-
if (this.recoverer != null) {
121-
ItemRecovererCallback recoveryCallback = new ItemRecovererCallback(invocation.getArguments(),
122-
this.recoverer);
123-
try {
124-
Object recovered = this.retryOperations.execute(retryCallback, recoveryCallback);
125-
return recovered;
126-
}
127-
finally {
128-
RetryContext context = RetrySynchronizationManager.getContext();
129-
if (context != null) {
130-
context.removeAttribute("__proxy__");
131-
}
110+
RecoveryCallback<Object> recoveryCallback = (this.recoverer != null)
111+
? new ItemRecovererCallback(invocation.getArguments(), this.recoverer) : null;
112+
try {
113+
return this.retryOperations.execute(retryCallback, recoveryCallback);
114+
}
115+
finally {
116+
RetryContext context = RetrySynchronizationManager.getContext();
117+
if (context != null) {
118+
context.removeAttribute("__proxy__");
132119
}
133120
}
134-
135-
return this.retryOperations.execute(retryCallback);
136-
137121
}
138122

139-
/**
140-
* @author Dave Syer
141-
*
142-
*/
143-
private static final class ItemRecovererCallback implements RecoveryCallback<Object> {
144-
145-
private final Object[] args;
146-
147-
private final MethodInvocationRecoverer<?> recoverer;
148-
149-
/**
150-
* @param args the item that failed.
151-
*/
152-
private ItemRecovererCallback(Object[] args, MethodInvocationRecoverer<?> recoverer) {
153-
this.args = Arrays.asList(args).toArray();
154-
this.recoverer = recoverer;
155-
}
123+
private record ItemRecovererCallback(Object[] args,
124+
MethodInvocationRecoverer<?> recoverer) implements RecoveryCallback<Object> {
156125

157126
@Override
158127
public Object recover(RetryContext context) {

src/main/java/org/springframework/retry/support/RetryTemplate.java

+12-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2006-2022 the original author or authors.
2+
* Copyright 2006-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -317,6 +317,9 @@ protected <T, E extends Throwable> T doExecute(RetryCallback<T, E> retryCallback
317317
}
318318
}
319319

320+
Object label = retryCallback.getLabel();
321+
String labelMessage = (label != null) ? "; for: '" + label + "'" : "";
322+
320323
/*
321324
* We allow the whole loop to be skipped if the policy or context already
322325
* forbid the first try. This is used in the case of external retry to allow a
@@ -327,7 +330,7 @@ protected <T, E extends Throwable> T doExecute(RetryCallback<T, E> retryCallback
327330

328331
try {
329332
if (this.logger.isDebugEnabled()) {
330-
this.logger.debug("Retry: count=" + context.getRetryCount());
333+
this.logger.debug("Retry: count=" + context.getRetryCount() + labelMessage);
331334
}
332335
// Reset the last exception, so if we are successful
333336
// the close interceptors will not think we failed...
@@ -355,22 +358,23 @@ protected <T, E extends Throwable> T doExecute(RetryCallback<T, E> retryCallback
355358
backOffPolicy.backOff(backOffContext);
356359
}
357360
catch (BackOffInterruptedException ex) {
358-
lastException = e;
359361
// back off was prevented by another thread - fail the retry
360362
if (this.logger.isDebugEnabled()) {
361-
this.logger.debug("Abort retry because interrupted: count=" + context.getRetryCount());
363+
this.logger.debug("Abort retry because interrupted: count=" + context.getRetryCount()
364+
+ labelMessage);
362365
}
363366
throw ex;
364367
}
365368
}
366369

367370
if (this.logger.isDebugEnabled()) {
368-
this.logger.debug("Checking for rethrow: count=" + context.getRetryCount());
371+
this.logger.debug("Checking for rethrow: count=" + context.getRetryCount() + labelMessage);
369372
}
370373

371374
if (shouldRethrow(retryPolicy, context, state)) {
372375
if (this.logger.isDebugEnabled()) {
373-
this.logger.debug("Rethrow in retry for policy: count=" + context.getRetryCount());
376+
this.logger
377+
.debug("Rethrow in retry for policy: count=" + context.getRetryCount() + labelMessage);
374378
}
375379
throw RetryTemplate.<E>wrapIfNecessary(e);
376380
}
@@ -388,7 +392,7 @@ protected <T, E extends Throwable> T doExecute(RetryCallback<T, E> retryCallback
388392
}
389393

390394
if (state == null && this.logger.isDebugEnabled()) {
391-
this.logger.debug("Retry failed last attempt: count=" + context.getRetryCount());
395+
this.logger.debug("Retry failed last attempt: count=" + context.getRetryCount() + labelMessage);
392396
}
393397

394398
exhausted = true;
@@ -525,7 +529,7 @@ private RetryContext doOpenInternal(RetryPolicy retryPolicy) {
525529
/**
526530
* Actions to take after final attempt has failed. If there is state clean up the
527531
* cache. If there is a recovery callback, execute that and return its result.
528-
* Otherwise throw an exception.
532+
* Otherwise, throw an exception.
529533
* @param recoveryCallback the callback for recovery (might be null)
530534
* @param context the current retry context
531535
* @param state the {@link RetryState}

0 commit comments

Comments
 (0)