Spring retry - ways to integrate with your project
source link: http://www.java-allandsundry.com/2014/12/spring-retry-ways-to-integrate-with.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Spring retry - ways to integrate with your project
If you have a need to implement robust retry logic in your code, a proven way would be to use the spring retry library. My objective here is not to show how to use the spring retry project itself, but in demonstrating different ways that it can be integrated into your codebase.
Consider a service to invoke an external system:
package
retry.service;
public
interface
RemoteCallService {
String call()
throws
Exception;
}
@Bean
public
RemoteCallService remoteCallService()
throws
Exception {
RemoteCallService remoteService = mock(RemoteCallService.
class
);
when(remoteService.call())
.thenThrow(
new
RuntimeException(
"Remote Exception 1"
))
.thenThrow(
new
RuntimeException(
"Remote Exception 2"
))
.thenReturn(
"Completed"
);
return
remoteService;
}
And this is the test for the retry logic:
public
class
SpringRetryTests {
@Autowired
private
RemoteCallService remoteCallService;
@Test
public
void
testRetry()
throws
Exception {
String message =
this
.remoteCallService.call();
verify(remoteCallService, times(
3
)).call();
assertThat(message, is(
"Completed"
));
}
}
If we were to directly incorporate spring-retry at the point of calling this service, then the code would have looked like this:
@Test
public
void
testRetry()
throws
Exception {
String message =
this
.retryTemplate.execute(context ->
this
.remoteCallService.call());
verify(remoteCallService, times(
3
)).call();
assertThat(message, is(
"Completed"
));
}
Given this, the following are the approaches to incorporate Spring-retry logic.
Approach 1: Custom Aspect to incorporate Spring-retry
This approach should be fairly intuitive as the retry logic can be considered a cross cutting concern and a good way to implement a cross cutting concern is using Aspects. An aspect which incorporates the Spring-retry would look something along these lines:package
retry.aspect;
import
org.aspectj.lang.ProceedingJoinPoint;
import
org.aspectj.lang.annotation.Around;
import
org.aspectj.lang.annotation.Aspect;
import
org.aspectj.lang.annotation.Pointcut;
import
org.slf4j.Logger;
import
org.slf4j.LoggerFactory;
import
org.springframework.beans.factory.annotation.Autowired;
import
org.springframework.retry.support.RetryTemplate;
@Aspect
public
class
RetryAspect {
private
static
Logger logger = LoggerFactory.getLogger(RetryAspect.
class
);
@Autowired
private
RetryTemplate retryTemplate;
@Pointcut
(
"execution(* retry.service..*(..))"
)
public
void
serviceMethods() {
//
}
@Around
(
"serviceMethods()"
)
public
Object aroundServiceMethods(ProceedingJoinPoint joinPoint) {
try
{
return
retryTemplate.execute(retryContext -> joinPoint.proceed());
}
catch
(Throwable e) {
throw
new
RuntimeException(e);
}
}
}
Approach 2: Using Spring-retry provided advice
Out of the box Spring-retry project provides an advice which takes care of ensuring that targeted services can be retried. The AOP configuration to weave the advice around the service requires dealing with raw xml as opposed to the previous approach where the aspect can be woven using Spring Java configuration. The xml configuration looks like this:<?xml version=
"1.0"
encoding=
"UTF-8"
?>
<beans xmlns=
"http://www.springframework.org/schema/beans"
xmlns:xsi=
"http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop=
"http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http:
//www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http:
//www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<aop:config>
<aop:pointcut id=
"transactional"
expression=
"execution(* retry.service..*(..))"
/>
<aop:advisor pointcut-ref=
"transactional"
advice-ref=
"retryAdvice"
order=
"-1"
/>
</aop:config>
</beans>
Approach 3: Declarative retry logic
This is the recommended approach, you will see that the code is far more concise than with the previous two approaches. With this approach, the only thing that needs to be done is to declaratively indicate which methods need to be retried:package
retry.service;
import
org.springframework.retry.annotation.Backoff;
import
org.springframework.retry.annotation.Retryable;
public
interface
RemoteCallService {
@Retryable
(maxAttempts =
3
, backoff =
@Backoff
(delay =
2000
))
String call()
throws
Exception;
}
package
retry;
import
org.junit.Test;
import
org.junit.runner.RunWith;
import
org.springframework.beans.factory.annotation.Autowired;
import
org.springframework.context.annotation.Bean;
import
org.springframework.context.annotation.Configuration;
import
org.springframework.retry.annotation.EnableRetry;
import
org.springframework.test.context.ContextConfiguration;
import
org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import
retry.service.RemoteCallService;
import
static
org.hamcrest.MatcherAssert.assertThat;
import
static
org.hamcrest.Matchers.is;
import
static
org.mockito.Mockito.*;
@RunWith
(SpringJUnit4ClassRunner.
class
)
@ContextConfiguration
public
class
SpringRetryDeclarativeTests {
@Autowired
private
RemoteCallService remoteCallService;
@Test
public
void
testRetry()
throws
Exception {
String message =
this
.remoteCallService.call();
verify(remoteCallService, times(
3
)).call();
assertThat(message, is(
"Completed"
));
}
@Configuration
@EnableRetry
public
static
class
SpringConfig {
@Bean
public
RemoteCallService remoteCallService()
throws
Exception {
RemoteCallService remoteService = mock(RemoteCallService.
class
);
when(remoteService.call())
.thenThrow(
new
RuntimeException(
"Remote Exception 1"
))
.thenThrow(
new
RuntimeException(
"Remote Exception 2"
))
.thenReturn(
"Completed"
);
return
remoteService;
}
}
}
The @EnableRetry annotation activates the processing of @Retryable annotated methods and internally uses logic along the lines of approach 2 without the end user needing to be explicit about it.
I hope this gives you a slightly better taste for how to incorporate Spring-retry in your project. All the code that I have demonstrated here is also available in my github project here: https://github.com/bijukunjummen/test-spring-retry
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK