• 注册
  • 后端开发博客 后端开发博客 关注:0 内容:2417

    Spring框架中一个有用的小组件:Spring Retry

  • 查看作者
  • 打赏作者
  • 当前位置: 职业司 > 后端开发 > 后端开发博客 > 正文
    • 后端开发博客
    • 1、概述

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

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

      <!--more-->

      2、Maven依赖

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

      <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-retryspring-aspects 依赖项。

      3、开启Spring Retry

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

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

      4、使用Spring Retry

      4.1、@Retryable而不用恢复

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

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

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

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

      4.2、@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.3、自定义@Retryable的行为

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

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

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

      4.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

      5、RetryTemplate

      5.1、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.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用于控制重试尝试之间的回退。

      最后,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、监听器

      监听器在重试时提供另外的回调。我们可以用这些来关注跨不同重试的各个横切点。

      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、注册监听器

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

      @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  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
      

      8、结论

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

      请登录之后再进行评论

      登录

      手机阅读天地(APP)

      • 微信公众号
      • 微信小程序
      • 安卓APP
      手机浏览,惊喜多多
      匿名树洞,说我想说!
      问答悬赏,VIP可见!
      密码可见,回复可见!
      即时聊天、群聊互动!
      宠物孵化,赠送礼物!
      动态像框,专属头衔!
      挑战/抽奖,金币送不停!
      赶紧体会下,不会让你失望!
    • 实时动态
    • 签到
    • 做任务
    • 发表内容
    • 偏好设置
    • 到底部
    • 帖子间隔 侧栏位置:
    • 还没有账号?点这里立即注册