Spring框架中一个颇有意思的小组件:Spring Retry

一、概述

Spring Retry 是Spring框架中的一个组件,
它提供了自动从新调用失败操做的能力。这在错误多是暂时发生的(如瞬时网络故障)的状况下颇有帮助。spring

在本文中,咱们将看到使用Spring Retry的各类方式:注解、RetryTemplate以及回调。sql

二、Maven依赖

让咱们首先将spring-retry依赖项添加到咱们的pom.xml文件中:markdown

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
    <version>1.2.5.RELEASE</version>
</dependency>

咱们还须要将Spring AOP添加到咱们的项目中:网络

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.2.8.RELEASE</version>
</dependency>

能够查看Maven Central来获取最新版本的spring-retry
spring-aspects 依赖项。框架

三、开启Spring Retry

要在应用程序中启用Spring Retry,咱们须要将@EnableRetry注释添加到咱们的@Configuration类:maven

@Configuration
@EnableRetry
public class AppConfig { ... }

四、使用Spring Retry

4.一、@Retryable而不用恢复

咱们可使用@Retryable注解为方法添加剧试功能:ide

@Service
public interface MyService {
    @Retryable(value = RuntimeException.class)
    void retryService(String sql);

}

在这里,当抛出RuntimeException时尝试重试。测试

根据@Retryable的默认行为,重试最多可能发生3次,重试之间有1秒的延迟。spa

4.二、@Retryable@Recover

如今让咱们使用@Recover注解添加一个恢复方法:日志

@Service
public interface MyService {
    @Retryable(value = SQLException.class)
    void retryServiceWithRecovery(String sql) throws SQLException;

    @Recover
    void recover(SQLException e, String sql);
}

这里,当抛出SQLException时重试会尝试运行。 当@Retryable方法因指定异常而失败时,@Recover注解定义了一个单独的恢复方法。

所以,若是retryServiceWithRecovery方法在三次尝试以后仍是抛出了SQLException,那么recover()方法将被调用。

恢复处理程序的第一个参数应该是Throwable类型(可选)和相同的返回类型。其他的参数按相同顺序从失败方法的参数列表中填充。

4.三、自定义@Retryable的行为

为了自定义重试的行为,咱们可使用参数maxAttemptsbackoff

@Service
public interface MyService {
    @Retryable( value = SQLException.class, 
      maxAttempts = 2, backoff = @Backoff(delay = 100))
    void retryServiceWithCustomization(String sql) throws SQLException;
}

这样最多将有两次尝试和100毫秒的延迟。

4.四、使用Spring Properties

咱们还能够在@Retryable注解中使用properties。

为了演示这一点,咱们将看到如何将delaymaxAttempts的值外部化到一个properties文件中。

首先,让咱们在名为retryConfig.properties的文件中定义属性:

retry.maxAttempts=2
retry.maxDelay=100

而后咱们指示@Configuration类加载这个文件:

@PropertySource("classpath:retryConfig.properties")
public class AppConfig { ... }
// ...

最后,咱们能够在@Retryable的定义中注入retry.maxAttemptsretry.maxDelay的值:

@Service 
public interface MyService { 
  @Retryable( value = SQLException.class, maxAttemptsExpression = "${retry.maxAttempts}",
            backoff = @Backoff(delayExpression = "${retry.maxDelay}")) 
  void retryServiceWithExternalizedConfiguration(String sql) throws SQLException; 
}

请注意,咱们如今使用的是maxAttemptsExpressiondelayExpression而不是maxAttemptsdelay

五、RetryTemplate

5.一、RetryOperations

Spring Retry提供了RetryOperations接口,它提供了一组execute()方法:

public interface RetryOperations {
    <T> T execute(RetryCallback<T> retryCallback) throws Exception;

    ...
}

execute()方法的参数RetryCallback,是一个接口,能够插入须要在失败时重试的业务逻辑:

public interface RetryCallback<T> {
    T doWithRetry(RetryContext context) throws Throwable;
}

5.二、RetryTemplate配置

RetryTemplateRetryOperations的一个实现。

让咱们在@Configuration类中配置一个RetryTemplate的bean:

@Configuration
public class AppConfig {
    //...
    @Bean
    public RetryTemplate retryTemplate() {
        RetryTemplate retryTemplate = new RetryTemplate();

        FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
        fixedBackOffPolicy.setBackOffPeriod(2000l);
        retryTemplate.setBackOffPolicy(fixedBackOffPolicy);

        SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
        retryPolicy.setMaxAttempts(2);
        retryTemplate.setRetryPolicy(retryPolicy);

        return retryTemplate;
    }
}

这个RetryPolicy肯定了什么时候应该重试操做。

其中SimpleRetryPolicy定义了重试的固定次数,另外一方面,BackOffPolicy用于控制重试尝试之间的回退。

最后,FixedBackOffPolicy会使重试在继续以前暂停一段固定的时间。

5.三、使用RetryTemplate

要使用重试处理来运行代码,咱们能够调用retryTemplate.execute()方法:

retryTemplate.execute(new RetryCallback<Void, RuntimeException>() {
    @Override
    public Void doWithRetry(RetryContext arg0) {
        myService.templateRetryService();
        ...
    }
});

咱们可使用lambda表达式代替匿名类:

retryTemplate.execute(arg0 -> {
    myService.templateRetryService();
    return null;
});

六、监听器

监听器在重试时提供另外的回调。咱们能够用这些来关注跨不一样重试的各个横切点。

6.一、添加回调

回调在RetryListener接口中提供:

public class DefaultListenerSupport extends RetryListenerSupport {
    @Override
    public <T, E extends Throwable> void close(RetryContext context,
      RetryCallback<T, E> callback, Throwable throwable) {
        logger.info("onClose");
        ...
        super.close(context, callback, throwable);
    }

    @Override
    public <T, E extends Throwable> void onError(RetryContext context,
      RetryCallback<T, E> callback, Throwable throwable) {
        logger.info("onError"); 
        ...
        super.onError(context, callback, throwable);
    }

    @Override
    public <T, E extends Throwable> boolean open(RetryContext context,
      RetryCallback<T, E> callback) {
        logger.info("onOpen");
        ...
        return super.open(context, callback);
    }
}

openclose的回调在整个重试以前和以后执行,而onError应用于单个RetryCallback调用。

6.二、注册监听器

接下来,咱们将咱们的监听器(DefaultListenerSupport)注册到咱们的RetryTemplate bean:

@Configuration
public class AppConfig {
    ...

    @Bean
    public RetryTemplate retryTemplate() {
        RetryTemplate retryTemplate = new RetryTemplate();
        ...
        retryTemplate.registerListener(new DefaultListenerSupport());
        return retryTemplate;
    }
}

七、测试结果

为了完成咱们的示例,让咱们验证一下结果:

@RunWith(SpringJUnit4Cla***unner.class)
@ContextConfiguration(
  classes = AppConfig.class,
  loader = AnnotationConfigContextLoader.class)
public class SpringRetryIntegrationTest {

    @Autowired
    private MyService myService;

    @Autowired
    private RetryTemplate retryTemplate;

    @Test(expected = RuntimeException.class)
    public void givenTemplateRetryService_whenCallWithException_thenRetry() {
        retryTemplate.execute(arg0 -> {
            myService.templateRetryService();
            return null;
        });
    }
}

从测试日志中能够看出,咱们已经正确配置了RetryTemplateRetryListener

2020-01-09 20:04:10 [main] INFO  c.p.s.DefaultListenerSupport - onOpen 
2020-01-09 20:04:10 [main] INFO  c.pinmost.springretry.MyServiceImpl - throw RuntimeException in method templateRetryService() 
2020-01-09 20:04:10 [main] INFO  c.p.s.DefaultListenerSupport - onError 
2020-01-09 20:04:12 [main] INFO  c.pinmost.springretry.MyServiceImpl - throw RuntimeException in method templateRetryService() 
2020-01-09 20:04:12 [main] INFO  c.p.s.DefaultListenerSupport - onError 
2020-01-09 20:04:12 [main] INFO  c.p.s.DefaultListenerSupport - onClose

八、结论

在本文中,咱们看到了如何使用注解、RetryTemplate和回调监听器来使用Spring Retry。

相关文章
相关标签/搜索