Skip to content

Commit 7f330e2

Browse files
authored
GH-10345: Migrate to Spring Core Retry (#10401)
* GH-10345: Migrate to Spring Core Retry Fixes: #10345 * Remove `spring-retry` dependency since all its usage have been replaced with Spring Core Retry and some custom internal implementations * Rework the `RequestHandlerRetryAdvice` to be based on the Spring Core Retry: - accept only `RetryPolicy` and use `RetryTemplate` internally just for stateless behavior - Expose `stateKeyFunction`, `newMessagePredicate` and `stateCacheSize` for stateful behavior. - Since there is no any stateful utility in Spring Core, implement its algorithm internally in the `RequestHandlerRetryAdvice` * Remove `RetryStateGenerator` and `SpelExpressionRetryStateGenerator` since they are based on `RetryState` from Spring Retry. And now they are replaced by the mentioned functions above as options on the `RequestHandlerRetryAdvice` * Rework `RetryAdviceParser` according to a new state of the `RequestHandlerRetryAdvice`. In addition, expose an `exponential-back-off/jitter` XML attribute * Fix `ErrorMessageStrategy` Javadoc to point to a general `AttributeAccessor` interface from Spring Core * Remove `ErrorMessageSendingRecoverer.RetryExceptionNotAvailableException` and the logic around it since this is not a case anymore with a new `RequestHandlerRetryAdvice` logic * Rework failing tests according to the new API and logic of the target classes * Document breaking change in the `whats-new.adoc` * Rework `retry` section in the `handler-advice/classes.adoc` to reflect new reality for the `RequestHandlerRetryAdvice` * Rework the configuration samples in the `retry` section to Java * Fix typos and language in the docs and Javadocs * Use a proper `retryStateKey` for cache entry removal on successful retry execution
1 parent b2810cd commit 7f330e2

File tree

17 files changed

+442
-722
lines changed

17 files changed

+442
-722
lines changed

build.gradle

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,6 @@ ext {
104104
springDataVersion = '2025.1.0-M5'
105105
springGraphqlVersion = '2.0.0-SNAPSHOT'
106106
springKafkaVersion = '4.0.0-SNAPSHOT'
107-
springRetryVersion = '2.0.12'
108107
springSecurityVersion = '7.0.0-SNAPSHOT'
109108
springVersion = '7.0.0-SNAPSHOT'
110109
springWsVersion = '5.0.0-SNAPSHOT'
@@ -485,9 +484,6 @@ project('spring-integration-core') {
485484
api 'org.springframework:spring-context'
486485
api 'org.springframework:spring-messaging'
487486
api 'org.springframework:spring-tx'
488-
api("org.springframework.retry:spring-retry:$springRetryVersion") {
489-
exclude group: 'org.springframework'
490-
}
491487
api 'io.projectreactor:reactor-core'
492488
api 'io.micrometer:micrometer-observation'
493489

spring-integration-core/src/main/java/org/springframework/integration/config/xml/RetryAdviceParser.java

Lines changed: 31 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.integration.config.xml;
1818

19+
import java.util.Set;
20+
1921
import org.w3c.dom.Element;
2022

2123
import org.springframework.beans.factory.support.AbstractBeanDefinition;
@@ -24,11 +26,9 @@
2426
import org.springframework.beans.factory.xml.ParserContext;
2527
import org.springframework.integration.handler.advice.ErrorMessageSendingRecoverer;
2628
import org.springframework.integration.handler.advice.RequestHandlerRetryAdvice;
27-
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
28-
import org.springframework.retry.backoff.FixedBackOffPolicy;
29-
import org.springframework.retry.policy.SimpleRetryPolicy;
30-
import org.springframework.retry.support.RetryTemplate;
3129
import org.springframework.util.StringUtils;
30+
import org.springframework.util.backoff.ExponentialBackOff;
31+
import org.springframework.util.backoff.FixedBackOff;
3232
import org.springframework.util.xml.DomUtils;
3333

3434
/**
@@ -43,42 +43,41 @@ public class RetryAdviceParser extends AbstractBeanDefinitionParser {
4343
@Override
4444
protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
4545
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(RequestHandlerRetryAdvice.class);
46-
BeanDefinitionBuilder retryTemplateBuilder = BeanDefinitionBuilder.genericBeanDefinition(RetryTemplate.class);
47-
boolean customTemplate = false;
46+
47+
String maxAttempts = element.getAttribute("max-attempts");
48+
49+
// Default to 3 attempts and no delay in between.
50+
BeanDefinitionBuilder backOffBuilder =
51+
BeanDefinitionBuilder.genericBeanDefinition(FixedBackOff.class)
52+
.addConstructorArgValue(0)
53+
.addConstructorArgValue(maxAttempts);
54+
4855
Element backOffPolicyEle = DomUtils.getChildElementByTagName(element, "fixed-back-off");
49-
BeanDefinitionBuilder backOffBuilder = null;
5056
if (backOffPolicyEle != null) {
51-
backOffBuilder = BeanDefinitionBuilder.genericBeanDefinition(ParsedFixedBackOffPolicy.class);
52-
IntegrationNamespaceUtils.setValueIfAttributeDefined(
53-
backOffBuilder, backOffPolicyEle, "interval", "backOffPeriodSimple");
57+
backOffBuilder = BeanDefinitionBuilder.genericBeanDefinition(FixedBackOff.class)
58+
.addConstructorArgValue(backOffPolicyEle.getAttribute("interval"))
59+
.addConstructorArgValue(maxAttempts);
5460
}
5561
else {
5662
backOffPolicyEle = DomUtils.getChildElementByTagName(element, "exponential-back-off");
5763
if (backOffPolicyEle != null) {
58-
backOffBuilder = BeanDefinitionBuilder.genericBeanDefinition(ParsedExponentialBackOffPolicy.class);
59-
IntegrationNamespaceUtils.setValueIfAttributeDefined(backOffBuilder, backOffPolicyEle, "initial",
60-
"initialIntervalSimple");
61-
IntegrationNamespaceUtils.setValueIfAttributeDefined(backOffBuilder, backOffPolicyEle,
62-
"multiplier", "multiplierSimple");
63-
IntegrationNamespaceUtils.setValueIfAttributeDefined(backOffBuilder, backOffPolicyEle, "maximum",
64-
"maxIntervalSimple");
64+
backOffBuilder = BeanDefinitionBuilder.genericBeanDefinition(ExponentialBackOff.class)
65+
.addPropertyValue("maxAttempts", maxAttempts)
66+
.addPropertyValue("multiplier", backOffPolicyEle.getAttribute("multiplier"))
67+
.addPropertyValue("initialInterval", backOffPolicyEle.getAttribute("initial"))
68+
.addPropertyValue("maxInterval", backOffPolicyEle.getAttribute("maximum"))
69+
.addPropertyValue("jitter", backOffPolicyEle.getAttribute("jitter"));
6570
}
6671
}
67-
if (backOffBuilder != null) {
68-
retryTemplateBuilder.addPropertyValue("backOffPolicy", backOffBuilder.getBeanDefinition());
69-
customTemplate = true;
70-
}
71-
String maxAttemptsAttr = element.getAttribute("max-attempts");
72-
if (StringUtils.hasText(maxAttemptsAttr)) {
73-
BeanDefinitionBuilder retryPolicyBuilder =
74-
BeanDefinitionBuilder.genericBeanDefinition(SimpleRetryPolicy.class)
75-
.addConstructorArgValue(element.getAttribute("max-attempts"));
76-
retryTemplateBuilder.addPropertyValue("retryPolicy", retryPolicyBuilder.getBeanDefinition());
77-
customTemplate = true;
78-
}
79-
if (customTemplate) {
80-
builder.addPropertyValue("retryTemplate", retryTemplateBuilder.getBeanDefinition());
81-
}
72+
73+
BeanDefinitionBuilder retryPolicyBuilder =
74+
BeanDefinitionBuilder.genericBeanDefinition("org.springframework.core.retry.DefaultRetryPolicy")
75+
.addConstructorArgValue(Set.of())
76+
.addConstructorArgValue(Set.of())
77+
.addConstructorArgValue(null)
78+
.addConstructorArgValue(backOffBuilder.getBeanDefinition());
79+
80+
builder.addPropertyValue("retryPolicy", retryPolicyBuilder.getBeanDefinition());
8281
String recoveryChannelAttr = element.getAttribute("recovery-channel");
8382
if (StringUtils.hasText(recoveryChannelAttr)) {
8483
BeanDefinitionBuilder emsrBuilder =
@@ -90,34 +89,4 @@ protected AbstractBeanDefinition parseInternal(Element element, ParserContext pa
9089
return builder.getBeanDefinition();
9190
}
9291

93-
private static final class ParsedFixedBackOffPolicy extends FixedBackOffPolicy {
94-
95-
ParsedFixedBackOffPolicy() {
96-
}
97-
98-
public void setBackOffPeriodSimple(long backOffPeriod) {
99-
setBackOffPeriod(backOffPeriod);
100-
}
101-
102-
}
103-
104-
private static final class ParsedExponentialBackOffPolicy extends ExponentialBackOffPolicy {
105-
106-
ParsedExponentialBackOffPolicy() {
107-
}
108-
109-
public void setInitialIntervalSimple(long initialInterval) {
110-
setInitialInterval(initialInterval);
111-
}
112-
113-
public void setMultiplierSimple(double multiplier) {
114-
setMultiplier(multiplier);
115-
}
116-
117-
public void setMaxIntervalSimple(long maxInterval) {
118-
setMaxInterval(maxInterval);
119-
}
120-
121-
}
122-
12392
}

spring-integration-core/src/main/java/org/springframework/integration/handler/advice/ErrorMessageSendingRecoverer.java

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,14 @@
1616

1717
package org.springframework.integration.handler.advice;
1818

19-
import java.io.Serial;
20-
2119
import org.jspecify.annotations.Nullable;
2220

2321
import org.springframework.core.AttributeAccessor;
2422
import org.springframework.integration.core.ErrorMessagePublisher;
2523
import org.springframework.integration.core.RecoveryCallback;
2624
import org.springframework.integration.support.DefaultErrorMessageStrategy;
2725
import org.springframework.integration.support.ErrorMessageStrategy;
28-
import org.springframework.integration.support.ErrorMessageUtils;
29-
import org.springframework.messaging.Message;
3026
import org.springframework.messaging.MessageChannel;
31-
import org.springframework.messaging.MessagingException;
3227

3328
/**
3429
* A {@link RecoveryCallback} that sends the final throwable as an
@@ -85,31 +80,4 @@ public ErrorMessageSendingRecoverer(MessageChannel channel, ErrorMessageStrategy
8580
return null;
8681
}
8782

88-
@Override
89-
protected Throwable payloadWhenNull(AttributeAccessor context) {
90-
Message<?> message = (Message<?>) context.getAttribute(ErrorMessageUtils.FAILED_MESSAGE_CONTEXT_KEY);
91-
String description = "No retry exception available; " +
92-
"this can occur, for example, if the RetryPolicy allowed zero attempts " +
93-
"to execute the handler; " +
94-
"RetryContext: " + context;
95-
return message == null
96-
? new RetryExceptionNotAvailableException(description)
97-
: new RetryExceptionNotAvailableException(message, description);
98-
}
99-
100-
public static class RetryExceptionNotAvailableException extends MessagingException {
101-
102-
@Serial
103-
private static final long serialVersionUID = 1L;
104-
105-
RetryExceptionNotAvailableException(String description) {
106-
super(description);
107-
}
108-
109-
public RetryExceptionNotAvailableException(Message<?> message, String description) {
110-
super(message, description);
111-
}
112-
113-
}
114-
11583
}

0 commit comments

Comments
 (0)