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

 

posted @ 2021-11-14 15:44  ジ绯色月下ぎ  阅读(523)  评论(0编辑  收藏  举报