spring-retry 使用示例(两种 命令式、声明式)
说明:业务开发过程中经常遇到需要对接第三方接口获取第三方结果,当遇到网络问题调用失败的时候,需要去重试调用。而spring-retry就提供了这样的支持
依赖:
<dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> </dependency>
主启动类开启注解 支持重试
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.retry.annotation.EnableRetry;
@EnableRetry
@SpringBootApplication
public class MsgApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(MsgApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(MsgApplication.class, args);
}
}
命令式
初始化重试模板,放到spring容器中
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.retry.backoff.FixedBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
/**
* 全局Bean定义
*/
@Configuration
@EnableRetry
public class RetryConfig {
/**
* Spring-retry 失败重试机制调用模板
* @return
*/
@Bean
public RetryTemplate retryTemplate() {
RetryTemplate retryTemplate = new RetryTemplate();
// 最大重试次数策略 默认3次 自定义6次
SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy(6);
retryTemplate.setRetryPolicy(simpleRetryPolicy);
FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
// 重试间隔 每隔1s后再重试
fixedBackOffPolicy.setBackOffPeriod(1000);
retryTemplate.setBackOffPolicy(fixedBackOffPolicy);
return retryTemplate;
}
}
具体的业务代码
import com.alibaba.fastjson.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.retry.RetryCallback; import org.springframework.retry.RetryContext; import org.springframework.retry.support.RetryTemplate; import org.springframework.stereotype.Service; import java.util.HashMap; import java.util.Map; @Service public class RetryServiceImpl implements RetryService { private static Logger logger = LoggerFactory.getLogger(RetryServiceImpl.class); @Autowired private RetryTemplate retryTemplate;
@Autowired private RestTemplate restTemplate; @Override public Map<String, Object> retrySendForJson(String url, String param) throws Exception { logger.info("第三方接口重试调用入参: url:{},param:{}", url, param); Map<String, Object> res = retryTemplate.execute(new RetryCallback<Map<String, Object>, Exception>() { @Override public Map<String, Object> doWithRetry(RetryContext context) throws Exception{ Map<String, Object> result = restTemplate.getForObject(url, Map.class, param); logger.info("第三方接口重试第"+context.getRetryCount()+"次调用结果:result:{}", JSONObject.toJSONString(result)); if ("0".equals((String)result.get("code")) ){ throw new Exception("调用接口失败,Http Code是"+result.get("httpCode")); } return result; } },context -> { logger.info("第三方接口重试调用兜底方法执行,重试了" + context.getRetryCount() + "次"); String message = context==null?"": context.getLastThrowable()==null?"": context.getLastThrowable().getMessage()==null?"":context.getLastThrowable().getMessage(); Map<String, Object> map = new HashMap<>(); map.put("code", "0"); map.put("httpCode", 0); map.put("msg",message); logger.info("第三方接口重试调用兜底方法执行结果: result:{}", JSONObject.toJSONString(map)); return map; }); return res; } }
注意 :doWithRetry方法体中 必须明确的抛出了异常,才会重试。如果doWithRetry方法体中只有一行调用别的方法的代码比如methodA,若methodA中将异常捕获了,则不会重试!!!
声明式
声明式的只需要在对应的业务代码出使用响应的注解即可
import com.alibaba.fastjson.JSON; import io.netty.channel.ConnectTimeoutException; import org.checkerframework.checker.units.qual.A; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.remoting.RemoteAccessException; import org.springframework.retry.RetryCallback; import org.springframework.retry.RetryContext; import org.springframework.retry.annotation.Backoff; import org.springframework.retry.annotation.EnableRetry; import org.springframework.retry.annotation.Recover; import org.springframework.retry.annotation.Retryable; import org.springframework.retry.policy.SimpleRetryPolicy; import org.springframework.retry.support.RetryTemplate; import org.springframework.stereotype.Service; import org.springframework.web.client.ResourceAccessException; import org.springframework.web.client.RestTemplate; import java.net.ConnectException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; @Service @Configuration @EnableRetry public class TestRetryService{ @Autowired private RestTemplate restTemplate; private AtomicInteger counts = new AtomicInteger(1); //maxAttempts最大重试次数 delay延时多久执行 默认单位ms multiplier 延时指数 第一次1s后,往后2s后 4s后 8s后 @Retryable(value = {Exception.class}, maxAttempts = 5, backoff = @Backoff(delay = 1000, multiplier = 2)) public String service(String url, String json) { System.out.println(counts.getAndIncrement()); if (counts.get() ==1){ url = "http://127.0.0.1:8081"; }else if (counts.get() ==2){ Map<String, Object> m = null; System.out.println(m.get("aa")); }else if (counts.get() ==3){ List<String> list = new ArrayList<>(); System.out.println(list.get(1)); }else if (counts.get() ==4){ int i = 1/0; } else{ url = "http://127.0.0.1:8080"; } HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.parseMediaType("application/json; charset=UTF-8")); HttpEntity<String> formEntity = new HttpEntity<>(json, headers); String result = restTemplate.postForObject(url, formEntity, String.class); System.out.println(result); //throw new RemoteAccessException("here" + counts.get()); return result; } // @Recover // public String recover(ConnectTimeoutException e) { // System.out.println("连接超时重试机制"+counts.get()+"次已完成,依然连接超时.."); // return "连接超时重试机制"+counts.get()+"次已完成,依然连接超时.."; // } // // @Recover // public String recover(RemoteAccessException e) { // System.out.println("远程地址不可用重试机制"+counts.get()+"次已完成,依然读取超时.."); // return "远程地址不可用重试机制"+counts.get()+"次已完成,依然读取超时.."; // } // // @Recover // public String recover(ConnectException e) { // System.out.println("连接失败重试机制"+counts.get()+"次已完成,依然读取超时..a"); // return "连接失败重试机制"+counts.get()+"次已完成,依然读取超时..a"; // } // // @Recover // public String recover(NullPointerException e){ // System.out.println("空指针异常失败重试"+counts.get()+"次已完成"); // return "空指针异常失败重试"+counts.get()+"次已完成"; // } @Recover public String recover(Exception e,String url, String json){ System.out.println("异常失败重试"+counts.get()+"次已完成"); System.out.println("参数url:"+url+",json"+json); if (e.getCause() instanceof ConnectException){ System.out.println("连接超时异常"); } return "异常失败重试"+counts.get()+"次已完成"; } }
注意:这里测试了不同的异常(空指针、数组下标越界、连接超时、等),进行不同的兜底异常处理,但是有点麻烦,所以直接针对Exception进行了处理,在最后一个recover中可以针对异常进行区别处理
官网:https://github.com/spring-projects/spring-retry
中文版:https://my.oschina.net/itsaysay/blog/3131501
其他相关优秀文章:
https://developer.aliyun.com/article/92899
https://blog.csdn.net/u010979642/article/details/114368258