Spring Retry 教程

1、概览

Spring Retry 提供了自动重新调用失败操作的能力。这在错误可能是短暂的(例如瞬时的网络故障)情况下非常有帮助。

本文将带你了解使用 Spring Retry 的各种方法:注解、RetryTemplate 和回调。

2、Maven 依赖

首先,在 pom.xml 文件中添加 spring-retry 以及 Spring AOP 依赖:

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
    <version>2.0.3</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>6.0.11</version>
</dependency>

你可以在 Maven Central 获取 spring-retryspring-aspects 依赖的最新版本。

3、启用 Spring Retry

要在应用中启用 Spring Retry,需要在 @Configuration 类中添加 @EnableRetry 注解:

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

4、使用 Spring Retry

4.1、@Retryable 注解

使用 @Retryable 注解为方法添加重试功能:

@Service
public interface MyService { 

    @Retryable 
    void retryService(String sql); 
}

由于没有指定任何异常,因此将尝试重试所有异常。此外,一旦达到最大尝试次数,但仍有异常,就会抛出 ExhaustedRetryException

根据 @Retryable 的默认行为,重试最多可进行三次,两次重试之间的延迟时间为一秒。

4.2、@Retryable@Recover 注解

使用 @Recover 注解添加一个恢复方法:

@Service
public interface MyService { 

    @Retryable(retryFor = SQLException.class)
    void retryServiceWithRecovery(String sql) throws SQLException; 

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

@Recover 注解定义了当 @Retryable 方法因指定异常而失败时的恢复方法。

因此,如果 retryServiceWithRecovery 方法在尝试三次后仍抛出 SqlException,就会调用 recover() 方法。

Recovery Handler 方法的第一个参数应为 Throwable(可选)类型,后续参数将按照失败方法的参数列表的相同顺序填充。且返回类型相同!

4.3、自定义 @Retryable 的行为

可以使用 maxAttemptsbackoff 参数来自定义重试行为:

@Service
public interface MyService {

    @Retryable(retryFor = SQLException.class, maxAttempts = 2, backoff = @Backoff(delay = 100))
    void retryServiceWithCustomization(String sql) throws SQLException;
}

如上,最多可尝试 2 次,延迟时间为 100 毫秒。

4.4、使用 Spring Properties

还可以在 @Retryable 注解中使用 Properties。

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

retry.maxAttempts=2 # 最大尝试次数
retry.maxDelay=100 # 延迟

然后,通过 @Configuration 类加载该文件:

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

最后,在 @Retryable 注入 Properties 文件中定义的 retry.maxAttemptsretry.maxDelay

@Service 
public interface MyService {

    @Retryable(retryFor = SQLException.class, maxAttemptsExpression = "${retry.maxAttempts}",
               backoff = @Backoff(delayExpression = "${retry.maxDelay}")) 
    void retryServiceWithExternalConfiguration(String sql) throws SQLException; 
}

注意,使用的是 maxAttemptsExpressiondelayExpression 属性,而不是 maxAttemptsdelay

5、RetryTemplate

5.1、RetryOperations

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

public interface RetryOperations {

    <T> T execute(RetryCallback<T> retryCallback) throws Exception;

    ...
}

execute() 中的 RetryCallback<T> (重试回调)参数,它是一个接口,允许插入需要在失败时重试的业务逻辑:

public interface RetryCallback<T> {

    T doWithRetry(RetryContext context) throws Throwable;
}

5.2、 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 用于控制重试尝试之间的回退(backoff)。

最后,FixedBackOffPolicy 会在继续之前暂停固定的时间段。

5.3、使用 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、Listener

Listener(监听器)会在重试时提供额外的回调,可以利用这些回调处理不同重试中的各种横切关注点(cross-cutting concerns)。

6.1、添加回调

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.2、注册 Listener

接下来,向 RetryTemplate Bean 注册监听器(DefaultListenerSupport):

@Configuration
public class AppConfig {
    ...

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

7、测试结果

最后,来验证一下结果:

@RunWith(SpringJUnit4ClassRunner.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  o.b.s.DefaultListenerSupport - onOpen 
2020-01-09 20:04:10 [main] INFO  o.baeldung.springretry.MyServiceImpl
- throw RuntimeException in method templateRetryService() 
2020-01-09 20:04:10 [main] INFO  o.b.s.DefaultListenerSupport - onError 
2020-01-09 20:04:12 [main] INFO  o.baeldung.springretry.MyServiceImpl
- throw RuntimeException in method templateRetryService() 
2020-01-09 20:04:12 [main] INFO  o.b.s.DefaultListenerSupport - onError 
2020-01-09 20:04:12 [main] INFO  o.b.s.DefaultListenerSupport - onClose

8、总结

本文介绍了如何使用注解、RetryTemplate 和回调 Listener(监听器)来使用 Spring Retry。


Ref:https://www.baeldung.com/spring-retry