一、@Retryable注解
注解方式实现重试机制比较简单,只需要我们在需要重试的方法上加入以下注解
@Retryable(value = {RemoteAccessException.class}, maxAttempts = 3, backoff = @Backoff(delay = 1000))
- value:指定需要重试的异常类型。在这个例子中,我们指定了 RemoteAccessException 类型的异常。
- maxAttempts:指定最大重试次数。在这个例子中,我们指定了最大重试次数为 3。
- backoff:指定重试之间的时间间隔。在这个例子中,我们指定了 1000 毫秒的时间间隔。
当然异常类型可以写成Exception.class,这样当方法抛出任何异常都会重试了。
使用注解的方式实现重试机制需要注意以下几点:
1.首先是启动类加上@EnableReTry注解
2. 要求需要重试的方法和调用它的方法不能同属一个类中,因为@Retryable注解是通过切面实现的,说白了就是带有@Retryable注解的方法和调用他的方法都要被spring管理,属于不同的类即可。
3.@Retryable只能实现重试机制,重试次数达到上限后,还是失败,此时如果有需要回调处理的逻辑,需要另外一个注解@Recover
@Retryable(value = {SQLException.class}, maxAttempts = 3, backoff = @Backoff(delay = 100))
public void myMethod() throws SQLException {
// Some database operation that may throw a SQLException
}
@Recover
public void recover(SQLException e) {
// Recovery logic goes here
}
要求@Retryable
和@Recover
必须定义在一个类当中,这一点和上面调用@Retryable的方法必须分属不同的类正好相反,同时也要求@Retryable注解的方法不能有返回值,否则@Recover不生效
那如果一个类中有多个@Recover和@Retryable怎么区分?
很简单,可以通过value
属性来区分它们。value
属性允许您指定一个异常类型的数组,以区分在方法执行期间抛出的不同异常类型。
具体示例如下:
@Service
public class MyService {
@Retryable(value = {IOException.class})
public void methodA() throws IOException {
// Some code that may throw an IOException
}
@Recover
public void recoverA(IOException e) {
// Recovery logic for methodA() goes here
}
@Retryable(value = {NullPointerException.class})
public void methodB() throws NullPointerException {
// Some code that may throw a NullPointerException
}
@Recover
public void recoverB(NullPointerException e) {
// Recovery logic for methodB() goes here
}
}
笔者不想再单独写类了,想让调用@Retryable注解的方法和带有@Retryable注解本身的方法,两个方法属于一个类中,但是此时@Retryable不生效了,所以搜寻了另外一种方式,通过编码方法实现方法重试,当然也是spring提供的工具了,自己封装也不是不行,就是没那么好,考虑的不全面,请看第二种方式。
二、通过RetryTemplate
这种方式也是要求在启动类上添加@EnableReTry注解,如上图所示
1.RetryTemplate的配置类
package com.ruoyi.maintenance.thirdinterface.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
/**
* 重试配置类
* @author hulei
*/
@Configuration
@EnableRetry
public class RetryConfiguration {
@Bean
public RetryTemplate retryTemplate() {
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
//最大重试次数
retryPolicy.setMaxAttempts(5);
ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
//倍数
backOffPolicy.setMultiplier(2);
//初始间隔
backOffPolicy.setInitialInterval(3000);
RetryTemplate template = new RetryTemplate();
template.setRetryPolicy(retryPolicy);
template.setBackOffPolicy(backOffPolicy);
return template;
}
}
配置类主要是设置一些重试次数和重试时间等参数
有时候为了灵活使用,需要手动设置参数,这里提供了一个构建器 RetryTemplateBuilder
package com.ruoyi.maintenance.thirdinterface.builder; import org.springframework.retry.backoff.ExponentialBackOffPolicy; import org.springframework.retry.policy.SimpleRetryPolicy; import org.springframework.retry.support.RetryTemplate; /** * 重试模板构建器 * @author hulei */ public class RetryTemplateBuilder { /** * 最大重试次数 */ private int maxAttempts; /** * 重试间隔倍数(即第一次重试间隔为initialInterval,第二次重试间隔为initialInterval * backOffMultiplier,第三次重试间隔为initialInterval * backOffMultiplier^2) */ private double backOffMultiplier; /** * 初始间隔 */ private long initialInterval; public RetryTemplateBuilder withMaxAttempts(int maxAttempts) { this.maxAttempts = maxAttempts; return this; } public RetryTemplateBuilder withBackOffMultiplier(double backOffMultiplier) { this.backOffMultiplier = backOffMultiplier; return this; } public RetryTemplateBuilder withInitialInterval(long initialInterval) { this.initialInterval = initialInterval; return this; } public RetryTemplate build() { SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(); retryPolicy.setMaxAttempts(maxAttempts); ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy(); backOffPolicy.setMultiplier(backOffMultiplier); backOffPolicy.setInitialInterval(initialInterval); RetryTemplate retryTemplate = new RetryTemplate(); retryTemplate.setRetryPolicy(retryPolicy); retryTemplate.setBackOffPolicy(backOffPolicy); return retryTemplate; } }
使用如下
2.重试代码
我要重试的方法如下图
调用它的方法如下
引入retryTemplate
主要代码片段
try{
String result = retryTemplate.execute(
//RetryCallback,需要重试的业务逻辑
(RetryCallback<String, Throwable>) retryContext -> doPost(url, rsaEncryptHandle(jsonHandle(jsonObject,loop,platform).toJSONString())),
//RecoveryCallback兜底,多次重试失败后的处理逻辑,取最后一次重试抛出的异常信息,抛出异常
retryContext -> {
log.error("删除设备推送接口多次重试调用失败");
throw new RuntimeException(retryContext.getLastThrowable().getMessage());
}
);
log.info("删除设备远程调用返回结果:{}",result);
}catch (Throwable e){
log.error("删除远程调用异常:{}",e.getMessage());
}
核心是retryTemplate.excute调用
有两个参数RetryCallback和RecoveryCallback
1.RetryCallback
包装需要重试的逻辑,比较简单就是调用了我需要重试的方法doPost()
2.RecoveryCallback
重试次数达到上限,任然没有成功时的回调方法
笔者这里只是打印了并抛出了异常信息,如果有其他需要处理的异常回调逻辑,单独写一个方法,在这里调用即可。
总结:两种方式各有好处,看个人选择,如果需要编写的重试方法较多,建议使用注解方式,省时省力,单一场景使用,不想再写过多的类,使用第二种编码的方式