日常编码中,经常会遇到这样的场景:履行一次接口调用,如RPC调用,偶现失利,原因或许是接口超时、连接数耗尽、http网络抖动等,呈现反常时咱们并不能当即知道原因并作出反应,或许只是一个一般的RpcException或RuntimeException,这时分,咱们最常见的做法就是重试,本文将和咱们介绍一下如何正确完成重试。

一般咱们处理偶发反常的流程如下:反常时,

(1)循环的进行长途调用若干次数,记载一下调用失利的记载;
(2)休眠一段时刻,测验等候下流系统自恢复或释放连接数,持续循环调用失利的恳求;
(3)假如再调用失利、经过人工二次调用进行修正;

技能思想源于日子,让咱们从爱情中体会下如何规划一套高雅的重试机制:

1、从爱情行为中获取反应,成功的或是失利的(反常)

2、根据不同的反应决定要不要持续发展(重试)

3、不存在没有反常的代码,也没有一向心情稳定的伴侣,相识不易偶尔有冲突要懂得包容和了解(重试)。当然磨合不了也不能无休止的羁绊,要给予互相尊重和了解。要有底线,程序也要有兜底逻辑

4、多说一句哈或许两个人的处理不了的问题,是不是能够寻求更专业的第三方处理呢?所以重试到最终也能够考虑第三方的介入看是否能处理~~~

重试利器spring-retry【@Retryable注解】

回到代码咱们也同样会思考:

由于某些失利或许是由于网络短暂呈现了问题,只需重试一次或许较少次数就能够成功了,没必要人工介入。

关于由于短少要害参数导致事务处理不下去,或许其他原因导致事务处理不下去的状况,即使重试一万次也是失利,所以咱们不能无限制的重试下去,这也是有必要要加上重试次数的原因!

再举个比如。

咱们在项目中应该遇到过经过HttpClient调用第三方接口的场景,当你调用对方的接口失利之后,是直接抛出反常呢?仍是再次恳求呢?假如是再次恳求,运用的专门的重试结构,仍是简单一个for循环重试呢?

try {
         // 事务逻辑
} catch (Exception e) {
      for (int i = 0; i < 3; i++) {
         // 重试
      }
}

假如是上面这种办法,我只能说太low了,这种办法不光将重试的次数固定死了,并且每次都要写一个for循环,太繁琐。

所谓术业有专攻,专业的人干专业的事,重试就应该交给重试结构。下面为咱们介绍我常用的重试机制的工具:spring-retry (当然还有guava-retrying,咱们在这一篇文章中暂不做说明)

Talk is cheap Show me the code

想要运用这个重试结构,需求如下依靠:

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

并在项目的发动类上敞开重试功用:

@EnableRetry
@EnableAspectJAutoProxy
@SpringBootApplication
public class RetryDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(RetryDemoApplication.class, args);
    }
}

Retryable注解

标示一个办法被重试。

recover特点

指定重试失利时的兜底处理办法的办法名。

该办法有必要被Recover注解标示,且有必要和重试办法在同一个类中。

interceptor特点

重试办法运用的拦截器的bean称号。

与exclude互斥。

value特点

需求进行重试的反常,和includes意思相同。

也就是说,咱们能够指定抛出哪些反常时,进行重试。

默以为空(假如excludes也为空,则重试一切反常)。

include特点

需求进行重试的反常,和value意思相同。

也就是说,咱们能够指定抛出哪些反常时,进行重试。

默以为空(假如excludes也为空,则重试一切反常)。

exclude特点

指定不需求重试的反常。

假如有些反常是咱们不想要重试的,能够经过这个特点指定这些反常。

默以为空(假如include也为空,则重试一切反常)。

假如include为空但exclude为非空,则重试一切未排除的反常。

label特点

为了计算重试相关信息,咱们能够给每个重试办法供给一个仅有的标签。

假如没有供给,调用者能够挑选疏忽它,或许供给默许值。

stateful特点

重试是否有状态。

会从头抛出反常,但运用相同的战略将重试战略运用于具有相同参数的后续调用。

假如为false,则不会从头引发可重试的反常。

留意!!!

长途办法调用的时分不需求设置本特点,由于长途办法调用不涉及事务。

可是,当反常引发事务失效的时分需求留意这个特点。

在涉及到数据库更新操作的时分需求设置该特点值为true,抛出反常时,反常会往外抛,导致事务回滚,重试的时分会启用一个新的事务。

maxAttempts特点

指定最大重试次数(包含第一次失利)。

默许是3次。

maxAttemptsExpression特点

经过表达式指定最大重试次数(包含第一次失利)。

默许是3次。

类似,maxAttemptsExpression =”${retry.attempts}”等等

backoff特点

逃避战略,默以为空。

该参数为空时,表明失利当即重试,重试的时分会阻塞线程

exceptionExpression特点

反常处理表达式。

ExpressionRetryPolicy中运用,履行完父类的 canRetry(SimpleRetryPolicy.canRetry) 之后,需求校验 exceptionExpression 的值,为 true 则能够重试,才会触发重试机制。

能够经过这个特点指定表达式用于有条件地抑制重试。

假如抛出多个反常,只会检查最终那个。

能够在exceptionExpression中指定其他Bean。

例如,”message.contains(‘you can retry this’)”、 “@someBean.shouldRetry(#root)”。

listeners特点

指定要运用的重试侦听器的Bean称号,而不是Spring上下文中界说的默许称号。

Backoff注解

重试回退战略,失利后当即重试仍是等一会再重试。

value特点

失利后推迟(等候)重试的时刻(单位:毫秒)。

默许值是1000毫秒。

和delay特点意思相同。

假如delay不是0,则以delay为准。假如delay是0,则以value为准。

当没有设置multiplier时,表明每隔value毫秒重试,直到重试次数到达maxAttempts设置的最大答应重试次数。当设置了multiplier参数时,该值作为幂运算的初始值。

delay特点

同value特点。

maxDelay特点

重试之间的最大等候时刻(毫秒)。

假如该值小于delay,则默许值是30000。

multiplier特点

指定推迟的倍数。

假如为正,作为乘数用于计算下次推迟的时刻。

计算公式:当时推迟时刻= 上一次推迟时刻 * multiplier。

delayExpression特点

指定推迟时刻的表达式。

maxDelayExpression特点

指定最大推迟时刻的表达式。

multiplierExpression特点

指定multiplier的表达式。

random特点

是否启用随机退避战略。

默许false。

设置为true时表明启用随即退避战略,重试推迟时刻将是介于delay和maxDelay间的一个随机数。

设置该参数的目的是重试的时分防止一起建议重试恳求,造成DDOS进犯。

randomExpression特点

指定启用随机退避战略的表达式。

Recover注解

标示重试失利时的兜底办法。

用于在悉数重试失利时,进行兜底处理。

返回值类型有必要和@Retryable润饰的办法返回值类型完全一样。

且被本注解标示的办法的第一个参数有必要是Throwable 或许其子类,其他参数和@Retryable润饰的办法参数次序共同。

CircuitBreaker注解

断路器,用于标示重试办法或许类。

这个注解的特点和Retryable注解的特点差不多。

value特点

指定要重试的反常类型。

和include特点意思相同。

默以为空(假如excludes也为空,则重试一切反常)。

include特点

指定要重试的反常类型。

和value特点意思相同。

默以为空(假如excludes也为空,则重试一切反常)。

exclude特点

指定不需求重试的反常类型。

默以为空(假如include也为空,则重试一切反常)。

假如include为空但exclude为非空,则重试一切未被exclude指定的反常。

maxAttempts特点

最大重试次数(包含第一次失利)。

默许值是3。

maxAttemptsExpression特点

最大重试次数的表达式(包含第一次失利)。

默许值是3。

label特点

一个仅有的标签,用于计算重试相关信息。

默以为声明注解的办法签名。

resetTimeout特点

断路器重置前的超时(毫秒)。

默许20s。

假如断路器翻开的时刻超越resetTimeout,则在下一次调用时重置,以使下流组件有时机再次呼应。

resetTimeoutExpression特点

断路器重置前的超时表达式(毫秒)。

openTimeout特点

装备断路器翻开的超时时刻。

默许5s。

当超越openTimeout之后断路器变成半翻开状态(只需有一次重试成功,则闭合)。

在openTimeout时刻内,到达了重试次数到达了maxAttempts,且都失利了,电路将自动翻开,从而阻止访问下流组件。

openTimeoutExpression特点

经过表达式指定openTimeout。

exceptionExpression特点

反常处理表达式。

ExpressionRetryPolicy中运用,履行完父类的 canRetry(SimpleRetryPolicy.canRetry) 之后,需求校验 exceptionExpression 的值,为 true 则能够重试,才会触发重试机制。

能够经过这个特点指定表达式用于有条件地抑制重试。

假如抛出多个反常,只会检查最终那个。

能够在exceptionExpression中指定其他Bean。

例如,”message.contains(‘you can retry this’)”、 “@someBean.shouldRetry(#root)”。

listeners特点

指定要运用的重试侦听器的Bean称号,而不是Spring上下文中界说的默许称号。

了解以上内容,咱们就能够简单运用spring-retry重试结构了,更多的组合运用能够在运用熟悉的过程中不断摸索。

@Retryable(value = Exception.class, maxAttemptsExpression = "${retry.maxAttempts:3}",
        backoff = @Backoff(delayExpression = "${retry.delay:100}",
                maxDelayExpression = "${retry.maxDelay:10}",
                multiplierExpression = "${retry.multiplier:1}"),
        recover = "recover",
        listeners = {"smsRetryListener"})
@Override
public Boolean sendSsmExtend(String phone, String msg) {
    log.info("向 {} 发送短信:{}", phone, msg);
    throw new RuntimeException("发送短信失利");
}
@Recover
public Boolean recover(Throwable throwable, String phone, String msg) {
    log.info("进入recover办法......");
    log.info("注册成功后短信发送失利-存入DB-后续处理!!!......");
    return true;
}

举个完好的栗子:

代码示例: 注解办法

POM依靠

<dependencies>
	<dependency>
		<groupId>com.panda</groupId>
		<artifactId>common</artifactId>
		<version>0.0.1-SNAPSHOT</version>
	</dependency>
	<dependency>
		<groupId>org.springframework.retry</groupId>
		<artifactId>spring-retry</artifactId>
		<version>1.3.4</version>
	</dependency>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-aspects</artifactId>
		<version>5.3.23</version>
	</dependency>
	<dependency>
		<groupId>com.google.guava</groupId>
		<artifactId>guava</artifactId>
	</dependency>
	<dependency>
		<groupId>org.apache.commons</groupId>
		<artifactId>commons-lang3</artifactId>
	</dependency>
	<dependency>
		<groupId>org.apache.httpcomponents</groupId>
		<artifactId>httpclient</artifactId>
	</dependency>
	<dependency>
		<groupId>com.github.rholder</groupId>
		<artifactId>guava-retrying</artifactId>
		<version>2.0.0</version>
	</dependency>
</dependencies>

首要依靠是spring-retry、spring-aspects、guava-retrying。

RetryDemoApplication.java

package com.panda.retry;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.retry.annotation.EnableRetry;
@EnableRetry
@SpringBootApplication
public class RetryDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(RetryDemoApplication.class, args);
    }
}

项目发动类。

声明EnableRetry注解,敞开重试功用。

User.java

package com.panda.retry.entity;
import lombok.Data;
@Data
public class User {
    private String userName;
    private String password;
    private String phone;
}

用户信息实体类。

SsmService.java

package com.panda.retry.service;
import common.core.Result;
public interface SsmService {
    Result<Boolean> sendSsm(String phone, String msg);
}

短信服务接口,包含一个发送短信的办法。

UserService.java

package com.panda.retry.service;
import com.panda.retry.entity.User;
import common.core.Result;
public interface UserService {
    Result<Boolean> register(User user);
}

用户接口,包含一个注册办法。

UserServiceImpl.java

package com.panda.retry.service.impl;
import com.panda.retry.entity.User;
import com.panda.retry.service.SsmService;
import com.panda.retry.service.UserService;
import common.core.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Slf4j
@Service
public class UserServiceImpl implements UserService {
    @Resource
    private SsmService ssmService;
    @Override
    public Result<Boolean> register(User user) {
        log.info("保存用户信息......");
        log.info("初始化积分信息......");
        ssmService.sendSsm(user.getPhone(), "祝贺,用户注册成功!");
        return Result.success();
    }
}

用户接口完成类,完成用户注册功用。

在该功用中,会调用短信服务的发送短信功用。

SsmServiceImpl.java

package com.panda.retry.service.impl;
import com.panda.retry.service.SsmService;
import common.core.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class SsmServiceImpl implements SsmService {
    @Retryable(recover = "recover", maxAttempts = 5, backoff = @Backoff(value = 2000, multiplier = 2))
    @Override
    public Result<Boolean> sendSsm(String phone, String msg) {
        log.info("向 {} 发送短信:{}", phone, msg);
        throw new RuntimeException("发送短信失利");
    }
    @Recover
    public Result<Boolean> recover(Throwable throwable, String phone, String msg){
        log.info("进入recover办法......");
        return Result.success();
    }
}

短信服务完成类。

完成发送短信的功用。

经过Retryable注解标示sendSsm办法,使得短信在发送失利时,能够进行重试。首要指定了最大重试次数 5,逃避战略backoff ,以及兜底办法recover。

被Recover注解标示的办法是一个兜底办法,该办法签名有一定的要求,拜见上面的解说。

留意,Retryable注解经过recover特点指定的兜底办法有必要在当时类中!

包含以下几种:

NeverRetryPolicy

只答应调用RetryCallback一次,但不答应重试。

AlwaysRetryPolicy

总是答应重试。

运用不当会导致死循环,慎重运用!

SimpleRetryPolicy

固定次数的重试战略。

默许重试最大次数是3次,是RetryTemplate默许运用的战略。

ExpressionRetryPolicy

承继SimpleRetryPolicy,表达式重试战略。

TimeoutRetryPolicy

超时重试战略。

默许超时时刻为1秒,在指定的超时时刻内答应重试。

CircuitBreakerRetryPolicy

具有熔断功用的重试战略,需设置3个参数openTimeout、resetTimeout和delegate。

CompositeRetryPolicy

组合重试战略。

有两种组合办法:达观组合重试战略是指只需有一个战略答应重试就能够重试;悲观组合重试战略是指只需有一个战略不答应重试就不能重试。

ExceptionClassifierRetryPolicy

根据产生的反常挑选重试战略。

BackOffPolicy 退避战略

包含以下几种:

NoBackOffPolicy

无退避战略,重试时当即重试。

FixedBackOffPolicy

固定时刻的退避战略,需设置参数sleeper和backOffPeriod,前者指定等候战略,默许是Thread.sleep,即线程休眠;后者指定休眠时刻,默许1秒。

UniformRandomBackOffPolicy

随机时刻退避战略。

需设置sleeper、minBackOffPeriod和maxBackOffPeriod,该战略在[minBackOffPeriod, maxBackOffPeriod] 之间取一个随机休眠时刻,minBackOffPeriod默许500毫秒,maxBackOffPeriod默许1500毫秒。

ExponentialBackOffPolicy

指数退避战略。

需设置参数sleeper、initialInterval、maxInterval以及multiplier。

其中initialInterval指定初始休眠时刻,默许100毫秒;maxInterval指定最大休眠时刻,默许30秒,multiplier指定乘数,即下一次休眠时刻为当时休眠时刻 * multiplier;

ExponentialRandomBackOffPolicy

随机指数退避战略。

引进一个随机乘数,防止固定乘数或许会引起许多服务一起重试导致DDos的状况。

RetryConfig

package com.panda.retry.config;
import com.panda.retry.listener.MyRetryListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
import org.springframework.retry.backoff.ThreadWaitSleeper;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
@Configuration
public class RetryConfig {
    @Bean
    public RetryTemplate retryTemplate() throws InterruptedException {
        // 退避战略
        ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
        ThreadWaitSleeper sleeper = new ThreadWaitSleeper();
        sleeper.sleep(1);
        backOffPolicy.withSleeper(sleeper);
        backOffPolicy.setMultiplier(3D);
        backOffPolicy.setInitialInterval(1000);
        // 重试战略
        SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
        retryPolicy.setMaxAttempts(5);
        RetryTemplate retryTemplate = RetryTemplate.builder()
                .customPolicy(retryPolicy)
                .customBackoff(backOffPolicy)
                .retryOn(Exception.class)
                .build();
        retryTemplate.registerListener(new MyRetryListener());
        return retryTemplate;
    }
}

重试装备。

装备退避战略、重试战略、重试反常,并注册监听器。

MyRetryListener

package com.panda.retry.listener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.RetryListener;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class MyRetryListener implements RetryListener {
    @Override
    public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
        log.info("重试过程翻开了");
        return true;
    }
    @Override
    public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
        log.info("重试过程关闭了");
    }
    @Override
    public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
        log.info("重试过程发生过错");
    }
}

监听器,完成RetryListener接口。

在重试开始之前调用open办法,重试完毕后调用close办法。

重试抛出反常的时分调用onError办法。

SsmService

package com.panda.retry.service;
import common.core.Result;
public interface SsmService {
    Result<Boolean> sendSsm(String phone, String msg);
    Result<Boolean> sendSsm1(String phone, String msg);
    Result<Boolean> recover(Throwable throwable, String phone, String msg);
}

短信接口,新增了两个办法。

UserService

package com.panda.retry.service;
import com.panda.retry.entity.User;
import common.core.Result;
public interface UserService {
    Result<Boolean> register(User user);
    Result<Boolean> register1(User user);
}

用户接口,新增了一个办法。

SsmServiceImpl

package com.panda.retry.service.impl;
import com.panda.retry.service.SsmService;
import common.core.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class SsmServiceImpl implements SsmService {
    @Retryable(recover = "recover", maxAttempts = 5, backoff = @Backoff(value = 2000, multiplier = 2))
    @Override
    public Result<Boolean> sendSsm(String phone, String msg) {
        log.info("向 {} 发送短信:{}", phone, msg);
        throw new RuntimeException("发送短信失利");
    }
    @Override
    public Result<Boolean> sendSsm1(String phone, String msg) {
        log.info("向 {} 发送短信:{}", phone, msg);
        throw new RuntimeException("发送短信失利");
    }
    @Recover
    public Result<Boolean> recover(Throwable throwable, String phone, String msg) {
        log.info("进入recover办法......");
        return Result.success();
    }
}

短信完成类。

UserServiceImpl

package com.panda.retry.service.impl;
import com.panda.retry.entity.User;
import com.panda.retry.service.SsmService;
import com.panda.retry.service.UserService;
import common.core.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Slf4j
@Service
public class UserServiceImpl implements UserService {
    @Resource
    private SsmService ssmService;
    @Resource
    private RetryTemplate retryTemplate;
    @Override
    public Result<Boolean> register(User user) {
        log.info("保存用户信息......");
        log.info("初始化积分信息......");
        ssmService.sendSsm(user.getPhone(), "祝贺,用户注册成功!");
        return Result.success();
    }
    @Override
    public Result<Boolean> register1(User user) {
        log.info("保存用户信息......");
        log.info("初始化积分信息......");
        retryTemplate.execute(
                retry -> ssmService.sendSsm1(user.getPhone(), "祝贺,用户注册成功!"),
                recover -> ssmService.recover(recover.getLastThrowable(), user.getPhone(), "祝贺,用户注册成功!")
        );
        return Result.success();
    }
}

留意register1办法中的逻辑,经过retryTemplate的execute办法来完成重试逻辑和兜底战略。

UserController

package com.panda.retry.controller;
import com.panda.retry.entity.User;
import com.panda.retry.service.UserService;
import common.core.Result;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@RequestMapping("user")
public class UserController {
    @Resource
    private UserService userService;
    @PostMapping("register")
    public Result<Boolean> register(@RequestBody User user) {
        return userService.register(user);
    }
    @PostMapping("register1")
    public Result<Boolean> register1(@RequestBody User user) {
        return userService.register1(user);
    }
}

总结

本文为咱们介绍了spring-retry的两种用法:经过注解的办法,以及经过RetryTemplate编码的办法。并介绍了spring-retry的常用注解,重试战略以及退避战略等等。

一个重试模块应该具有以下特点:

(1)重试时履行的办法,这个办法履行成功后就不必再重试,这个办法要求幂等,并且失利时需求抛出反常来标识履行失利;

(2)重试战略:重试最大次数、重试最大时刻即XXms后不再重试、重试次数之间的距离、关于哪些反常才需求重试;

(3)重试次数到达上限仍未成功,最终的兜底逻辑,如刺进重试任务表、发送音讯给系统管理员等;

结合以上的思考,咱们看下spring-retry重试模块应该具有的特点及作用:

  1. RetryTemplate: 封装了Retry基本操作,是进入spring-retry结构的全体流程进口,经过RetryTemplate能够指定监听、回退战略、重试战略等。

  2. RetryCallback:该接口封装了事务代码,且failback后,会再次调用RetryCallback接口,直到到达重试次数/时刻上限;

  3. RecoveryCallback:当RetryCallback不能再重试的时分,假如界说了RecoveryCallback,就会调用RecoveryCallback,并以其返回成果作为最终的返回成果。此外,RetryCallback和RecoverCallback界说的接口办法都能够接收一个RetryContext上下文参数,经过它能够获取到测验次数、反常,也能够经过其setAttribute()和getAttribute()来传递一些信息。

  4. RetryPolicy:重试战略,描绘什么条件下能够测验重复调用RetryCallback接口;战略包含最大重试次数、指定反常调集/疏忽反常调集、重试答应的最大超时时刻;RetryTemplate内部默许时分用的是SimpleRetryPolicy,SimpleRetryPolicy默许将对一切反常进行测验,最多测验3次。还有其他多种更为杂乱功用更多的重试战略;

  5. BackOffPolicy:回退战略,用来界说在两次测验之间需求距离的时刻,如固定时刻距离、递增距离、随机距离等;RetryTemplate内部默许运用的是NoBackOffPolicy,其在两次测验之间不会进行任何的中止。关于一般可重试的操作往往是根据网络进行的长途恳求,它或许由于网络波动暂时不可用,假如立马进行重试它或许仍是不可用,可是中止一下,过一会再试或许它又恢复正常了,所以在RetryTemplate中运用BackOffPolicy往往是很有必要的;

  6. RetryListener:RetryTemplate中能够注册一些RetryListener,能够了解为是对重试过程中的一个增强,它能够在整个Retry前、整个Retry后和每次Retry失利时进行一些操作;假如只想重视RetryListener的某些办法,则能够挑选承继RetryListenerSupport,它默许完成了RetryListener的一切办法;

代码有反常需求重试,女朋友有心情也要记住哄哦。人无完人,爱情也需求debug,发现问题处理问题。

最终祝咱们有情人终成眷属 哈哈哈哈~~~~~~~~~~~~~~~~~~~~~~~~~~~~