如果您需要在代码中实现健壮的重试逻辑,一种行之有效的方法是使用spring重试库。 我的目的不是要展示如何使用spring retry项目本身,而是要展示可以将其集成到代码库中的不同方式。
考虑一种服务来调用外部系统:
package retry.service;public interface RemoteCallService {String call() throws Exception;
}
假定此调用可能失败,并且您希望每次调用失败都可以重试三次该调用,每次延迟2秒,所以为了模拟此行为,我已经使用Mockito定义了模拟服务,请注意,此返回为嘲笑的Spring bean:
@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;
}
因此,该模拟服务本质上将失败2次,并在第三个调用成功。
这是对重试逻辑的测试:
public class SpringRetryTests {@Autowiredprivate RemoteCallService remoteCallService;@Testpublic void testRetry() throws Exception {String message = this.remoteCallService.call();verify(remoteCallService, times(3)).call();assertThat(message, is("Completed"));}
}
我们确保该服务被调用3次,以解决前两个失败的呼叫以及成功的第三个呼叫。
如果我们在调用此服务时直接合并spring-retry,则代码将如下所示:
@Test
public void testRetry() throws Exception {String message = this.retryTemplate.execute(context -> this.remoteCallService.call());verify(remoteCallService, times(3)).call();assertThat(message, is("Completed"));
}
但是,这不是理想的选择,更好的包含方式是调用者不必明确知道存在重试逻辑的事实。
鉴于此,以下是合并Spring重试逻辑的方法。
方法1:自定义方面合并Spring重试
这种方法应该非常直观,因为可以将重试逻辑视为跨领域关注点,并且使用Aspects是实现跨领域关注点的好方法。 包含Spring重试的一个方面将遵循以下原则:
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);@Autowiredprivate 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);}}
}
这方面拦截了远程服务调用,并将该调用委托给retryTemplate。 完整的工作测试在这里 。
方法2:使用Spring-retry提供的建议
Spring-retry项目提供了开箱即用的建议,可确保确保可以重试目标服务。 围绕服务编织建议的AOP配置需要处理原始xml,这与以前的方法不同,前一种方法可以使用Spring Java配置来编织方面。 xml配置如下所示:
<?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.xsdhttp://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>
完整的工作测试在这里 。
方法3:声明式重试逻辑
这是推荐的方法,您将看到代码比前两种方法更加简洁。 使用这种方法,唯一需要做的就是声明性地指出需要重试的方法:
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 {@Autowiredprivate RemoteCallService remoteCallService;@Testpublic void testRetry() throws Exception {String message = this.remoteCallService.call();verify(remoteCallService, times(3)).call();assertThat(message, is("Completed"));}@Configuration@EnableRetrypublic static class SpringConfig {@Beanpublic 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;}}
}
@EnableRetry批注激活@Retryable批注方法的处理,并在内部使用方法2的逻辑,而最终用户无需明确说明。
我希望这会使您对如何将Spring-retry合并到项目中有所了解。 我在这里演示的所有代码也可以在我的github项目中找到 :https://github.com/bijukunjummen/test-spring-retry
翻译自: https://www.javacodegeeks.com/2014/12/spring-retry-ways-to-integrate-with-your-project.html