Spring Retry 重试
重试的使用场景比较多,比如调用远程服务时,由于网络或者服务端响应慢导致调用超时,此时可以多重试几次。用定时任务也可以实现重试的效果,但比较麻烦,用Spring Retry的话一个注解搞定所有。话不多说,先看演示。
首先引入依赖
<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
    <version>1.3.4</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.9.1</version>
</dependency>使用方式有两种:命令式和声明式
命令式
/**
 * 命令式的方式使用Spring Retry
 */
@GetMapping("/hello")
public String hello(@RequestParam("code") Integer code) throws Throwable {
    RetryTemplate retryTemplate = RetryTemplate.builder()
            .maxAttempts(3)
            .fixedBackoff(1000)
            .retryOn(RemoteAccessException.class)
            .build();
    retryTemplate.registerListener(new MyRetryListener());
    String resp = retryTemplate.execute(new RetryCallback<String, Throwable>() {
        @Override
        public String doWithRetry(RetryContext context) throws Throwable {
            return helloService.hello(code);
        }
    });
    return resp;
}定义一个RetryTemplate,然后调用execute方法,可配置项比较多,不一一列举
真正使用的时候RetryTemplate可以定义成一个Bean,例如:
@Configuration
public class RetryConfig {
    @Bean
    public RetryTemplate retryTemplate() {
        RetryTemplate retryTemplate = RetryTemplate.builder()
                .maxAttempts(3)
                .fixedBackoff(1000)
                .withListener(new MyRetryListener())
                .retryOn(RemoteAccessException.class)
                .build();
        return retryTemplate;
    }
}业务代码:
/**
 * 命令式的方式使用Spring Retry
 */
@Override
public String hello(int code) {
    if (0 == code) {
        System.out.println("出错了");
        throw new RemoteAccessException("出错了");
    }
    System.out.println("处理完成");
    return "ok";
}


监听器实现:
package com.example.retry.listener;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.RetryListener;
public class MyRetryListener implements RetryListener {
    @Override
    public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
        System.out.println("open");
        return true;
    }
    @Override
    public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
        System.out.println("close");
    }
    @Override
    public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
        System.out.println("error");
    }
}声明式(注解方式)
/**
 * 注解的方式使用Spring Retry
 */
@Retryable(value = Exception.class, maxAttempts = 2, backoff = @Backoff(value = 1000, delay = 2000, multiplier = 0.5))
@Override
public String hi(int code) {
    System.out.println("方法被调用");
    int a = 1/code;
    return "ok";
}
@Recover
public String hiRecover(Exception ex, int code) {
    System.out.println("重试结束");
    return "asdf";
}这里需要主要的几点
- @EnableRetry(proxyTargetClass = true/false)
- @Retryable 修饰的方法必须是public的,而且不能是同一个类中调用
- @Recover 修饰的方法签名必须与@Retryable修饰的方法一样,除了第一个参数外
/**
 * 注解的方式使用Spring Retry
 */
@GetMapping("/hi")
public String hi(@RequestParam("code") Integer code) {
    return helloService.hi(code);
}



1. 用法
声明式
@Configuration
@EnableRetry
public class Application {
}
@Service
class Service {
    @Retryable(RemoteAccessException.class)
    public void service() {
        // ... do something
    }
    @Recover
    public void recover(RemoteAccessException e) {
       // ... panic
    }
}命令式
RetryTemplate template = RetryTemplate.builder()
				.maxAttempts(3)
				.fixedBackoff(1000)
				.retryOn(RemoteAccessException.class)
				.build();
template.execute(ctx -> {
    // ... do something
});2. RetryTemplate
为了自动重试,Spring Retry 提供了 RetryOperations 重试操作策略
public interface RetryOperations {
    <T> T execute(RetryCallback<T> retryCallback) throws Exception;
    <T> T execute(RetryCallback<T> retryCallback, RecoveryCallback<T> recoveryCallback)
        throws Exception;
    <T> T execute(RetryCallback<T> retryCallback, RetryState retryState)
        throws Exception, ExhaustedRetryException;
    <T> T execute(RetryCallback<T> retryCallback, RecoveryCallback<T> recoveryCallback,
        RetryState retryState) throws Exception;
}基本回调是一个简单的接口,允许插入一些要重试的业务逻辑:
public interface RetryCallback<T> {
    T doWithRetry(RetryContext context) throws Throwable;
}回调函数被尝试,如果失败(通过抛出异常),它将被重试,直到成功或实现决定中止。
RetryOperations最简单的通用实现是RetryTemplate
RetryTemplate template = new RetryTemplate();
TimeoutRetryPolicy policy = new TimeoutRetryPolicy();
policy.setTimeout(30000L);
template.setRetryPolicy(policy);
Foo result = template.execute(new RetryCallback<Foo>() {
    public Foo doWithRetry(RetryContext context) {
        // Do stuff that might fail, e.g. webservice operation
        return result;
    }
});从Spring Retry 1.3开始,RetryTemplate支持流式配置:
RetryTemplate.builder()
      .maxAttempts(10)
      .exponentialBackoff(100, 2, 10000)
      .retryOn(IOException.class)
      .traversingCauses()
      .build();
RetryTemplate.builder()
      .fixedBackoff(10)
      .withinMillis(3000)
      .build();
RetryTemplate.builder()
      .infiniteRetry()
      .retryOn(IOException.class)
      .uniformRandomBackoff(1000, 3000)
      .build();3. RecoveryCallback
当重试耗尽时,RetryOperations可以将控制传递给不同的回调:RecoveryCallback。
Foo foo = template.execute(new RetryCallback<Foo>() {
    public Foo doWithRetry(RetryContext context) {
        // business logic here
    },
  new RecoveryCallback<Foo>() {
    Foo recover(RetryContext context) throws Exception {
          // recover logic here
    }
});4. Listeners
public interface RetryListener {
    void open(RetryContext context, RetryCallback<T> callback);
    void onSuccess(RetryContext context, T result);
    void onError(RetryContext context, RetryCallback<T> callback, Throwable e);
    void close(RetryContext context, RetryCallback<T> callback, Throwable e);
}在最简单的情况下,open和close回调在整个重试之前和之后,onSuccess和onError应用于个别的RetryCallback调用,onSuccess方法在成功调用回调之后被调用。
5. 声明式重试
有时,你希望在每次业务处理发生时都重试一些业务处理。这方面的典型例子是远程服务调用。Spring Retry提供了一个AOP拦截器,它将方法调用封装在RetryOperations实例中。RetryOperationsInterceptor执行被拦截的方法,并根据提供的RepeatTemplate中的RetryPolicy在失败时重试。
你可以在 @Configuration 类上添加一个 @EnableRetry 注解,并且在你想要进行重试的方法(或者类)上添加 @Retryable 注解,还可以指定任意数量的重试监听器。
@Configuration
@EnableRetry
public class Application {
    @Bean
    public Service service() {
        return new Service();
    }
    @Bean public RetryListener retryListener1() {
        return new RetryListener() {...}
    }
    @Bean public RetryListener retryListener2() {
        return new RetryListener() {...}
    }
}
@Service
class Service {
    @Retryable(RemoteAccessException.class)
    public service() {
        // ... do something
    }
}可以使用 @Retryable 的属性类控制 RetryPolicy 和 BackoffPolicy
@Service
class Service {
    @Retryable(maxAttempts=12, backoff=@Backoff(delay=100, maxDelay=500))
    public service() {
        // ... do something
    }
}如果希望在重试用尽时采用替代代码返回,则可以提供恢复方法。方法应该声明在与@Retryable实例相同的类中,并标记为@Recover。返回类型必须匹配@Retryable方法。恢复方法的参数可以包括抛出的异常和(可选地)传递给原始可重试方法的参数(或者它们的部分列表,只要在需要的最后一个之前不省略任何参数)。
@Service
class Service {
    @Retryable(RemoteAccessException.class)
    public void service(String str1, String str2) {
        // ... do something
    }
    @Recover
    public void recover(RemoteAccessException e, String str1, String str2) {
       // ... error handling making use of original args if required
    }
}若要解决可选择用于恢复的多个方法之间的冲突,可以显式指定恢复方法名称。
@Service
class Service {
    @Retryable(recover = "service1Recover", value = RemoteAccessException.class)
    public void service1(String str1, String str2) {
        // ... do something
    }
    @Retryable(recover = "service2Recover", value = RemoteAccessException.class)
    public void service2(String str1, String str2) {
        // ... do something
    }
    @Recover
    public void service1Recover(RemoteAccessException e, String str1, String str2) {
        // ... error handling making use of original args if required
    }
    @Recover
    public void service2Recover(RemoteAccessException e, String str1, String str2) {
        // ... error handling making use of original args if required
    }
}https://github.com/spring-projects/spring-retry
Spring Retry 重试的更多相关文章
- 自己动手实践 spring retry 重试框架
		前序 马上过年了,预祝大家,新年快乐,少写bug 什么是spring retry? spring retry是从spring batch独立出来的一个能功能,主要实现了重试和熔断. 什么时候用? 远程 ... 
- 012 spring retry重试原理的解析
		有点复杂,在后续的章节,将会对其中涉及到的知识点,再分章节进行说明. 1.程序结构 2.@Retryable package com.jun.web.annotation.theory; import ... 
- spring retry 重试机制完整例子
		public static Boolean vpmsRetryCoupon(final String userId) { // 构建重试模板实例 RetryTemplate retryTemplate ... 
- Spring异常重试框架Spring Retry
		Spring Retry支持集成到Spring或者Spring Boot项目中,而它支持AOP的切面注入写法,所以在引入时必须引入aspectjweaver.jar包. 快速集成的代码样例: @Con ... 
- 异常重试框架Spring Retry实践
		前期准备在Maven项目中添加Spring Retry和切面的依赖 POM: <!-- Spring Retry --> <dependency> <groupId> ... 
- Spring retry基本使用
		Spring retry基本使用 背景介绍 在实际工作过程中,重试是一个经常使用的手段.比如MQ发送消息失败,会采取重试手段,比如工程中使用RPC请求外部服务,可能因为网络 波动出现超时而采取重试手段 ... 
- Spring Retry
		最近组内准备将项目中原有的重试功能抽取出来重构为一个重试平台,由于对重试的功能要求比较高,采用了不少中间件和框架(jimdb,jproxy, Elastic-Job ,JMQ,Hbase, Disru ... 
- Spring retry实践
		在开发中,重试是一个经常使用的手段.比如MQ发送消息失败,会采取重试手段,比如工程中使用RPC请求外部服务,可能因为网络波动出现超时而采取重试手段......可以看见重试操作是非常常见的一种处理问题, ... 
- 【spring】spring retry介绍
		一.为什么需要重试? 我们知道只要是网络请求都有失败的情况,这个时候增加retry机制是必要的.而spring全家桶中就有这么一套机制. 二.spring retry spring系列的spring ... 
随机推荐
- java-运算符以及简单运用
			运算符: 1)赋值运算符:= 2)算术运算符:+-*/%,++,-- 3)关系运算符:>,<,>=,<=,==,!= boolean 4)逻辑运算符:&&,|| ... 
- Spring 01: Spring配置 + IOC控制反转 + Setter注入
			简介 Spring框架是一个容器,是整合其他框架的框架 他的核心是IOC(控制反转)和AOP(面向切面编程),由20多个模块构成,在很多领域都提供了优秀的问题解决方案 特点 轻量级:由20多个模块构成 ... 
- 用RocketMQ这么久,才知道消息可以这样玩
			在上一章节中,我们讲解了RocketMQ的基本介绍,作为MQ最重要的就是消息的使用了,今天我们就来带大家如何玩转MQ的消息. 消息中间件,英文Message Queue,简称MQ.它没有标准定义,一般 ... 
- python与pycharm的安装与“试用”
			python与pycharm的安装与"试用" 一.python解释器安装与启动 python解释器的安装 1.打开文件安装包运行页面 #python3.8 2.选择Customiz ... 
- 如何免费申请js.org二级域名
			最近看到很多人都去申请了js.org的域名,我就来写个教程吧! (本教程只注重于申请域名,而不是如何使用Github) 看看成品:https://butterfly.js.org/ 官网是这么写的: ... 
- 【JAVA】学习路径36-写到硬盘FileOutputStream Write的三种方法
			import java.io.FileOutputStream;import java.io.FileReader;import java.io.IOException;import java.nio ... 
- springboot配置(yami配置文件,JSR303数据校验,多环境配置)
			yami配置文件 YAML是 "YAML Ain't a Markup Language" (YAML不是一种标记语言)的递归缩写.在开发的这种语言时,YAML 的意思其实是:&q ... 
- C# winfrom ListView控件实现自由设置每一行字体及背景色等
			背景:公司经常会需要将日志信息,输出到一个对话框中显示出来.之前一直采用的listbox控件,操作简单,使用方便,但是遗憾的是,不能自由控制每一行的状态. 于是想了如下几个方案: (1)重绘listb ... 
- BI如何实现用户身份集成自定义安全程序开发
			统一身份认证是整个 IT 架构的最基本的组成部分,而账号则是实现统一身份认证的基础.做好账号的规划和设计直接决定着企业整个信息系统建设的便利与难易程度,决定着系统能否足够敏捷和快速赋能,也决定了在数字 ... 
- .Net下的Http请求调用(Post与Get)
			http请求调用是开发中经常会用到的功能.在内,调用自有项目的Web Api等形式接口时会用到:在外,调用一些第三方功能接口时,也会用到,因为,这些第三方功能往往是通过http地址的形式提供的,比如: ... 
