文章目录
- 1 问题背景
- 2 前言
- 3 多处重复的重试机制代码
- 4 优化后的代码
- 5 进一步优化
1 问题背景
在电商场景中,会调用很多第三方的云服务,比如发送邮件、发起支付、发送验证码等等。由于网络存在抖动,有时候发起调用后会拿到500的状态码,io exception等报错,因此需要重新调用,简称重试机制。项目中很多地方用到重试机制,导致很多重复的代码,因此笔者考虑使用Java8函数式接口优化该重试机制,抽成一个工具类方法。
2 前言
- 本文的代码中,可能有些类型没有给出代码,不需要纠结,主要了解函数式接口怎么应用即可
3 多处重复的重试机制代码
项目中多次出现的代码如下:
BasicResponse<String> response = null;int retryTimes = 0;do {try {String startTimeStr = DATE_TIME_FORMATTER.format(LocalDateTime.now());response = restTemplate.postForString(basicRequest); // 此行代码是可变的,可能是get方式请求,可能是post方式String endTimeStr = DATE_TIME_FORMATTER.format(LocalDateTime.now());PayReq logObject = PayReq.getLogObject(payReq);log.info("XXXPay payOrder, request:{}, response:{}, startTimeStr:{}, endTimeStr:{}, retryTimes:{}", JSON.toJSONString(logObject), JSON.toJSONString(response), startTimeStr, endTimeStr, retryTimes);} finally {if (response != null && !response.getCode().equals(HttpStatus.SC_OK)) {try {Thread.sleep(500L);} catch (InterruptedException e) {e.printStackTrace();}}retryTimes++;}} while (!response.getCode().equals(HttpStatus.SC_OK) && retryTimes < 3);
分析:
如上所示,在这行代码response = restTemplate.postForString(basicRequest);
是可变的,有可能是get方式提交http请求,有可能是post方式。因此要把此处抽象出来,交给调用者写具体实现。调用者需要拿到http响应报文,那么抽象出来的接口,需要有返回值。那么此处可以使用Supplier
函数式接口,或者自己定义一个有返回值的函数式接口也可以。
在log.info
打日志这行,需要打出响应报文、开始时间、结束时间、重试次数等,这些都可以抽到工具类里面,但是日志的内容XXXPay payOrder
这些是可变的,应该交由调用者写具体实现。那么我们可以定义一个函数式接口出来,有入参但无返回值,入参是提供给调用者使用的。
4 优化后的代码
定义一个打日志的函数式接口:
/*** 打日志的函数式接口* * @param <T>*/
@FunctionalInterface
public interface LogFunc<T> {/*** 打日志* * @param response 响应报文* @param startTimeStr http调用开始时间* @param endTimeStr http调用结束时间* @param curTime 当前重试次数*/void log(T response, String startTimeStr, String endTimeStr, int curTime);
}
Http重试工具类如下,主要关注有代码注释的那两处地方即可:
@Slf4j
public class HttpRetryUtil {private final static DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss:SSS");public static <T> T retryOnException(Supplier<T> supplier, LogFunc logFunc,int maxRetryTimes, long sleepMillis) {T result = null;int retryTimes = 0;do {try {String startTimeStr = LocalDateTime.now().format(DATE_TIME_FORMATTER);// 交给调用者写具体实现,并把值返回出去result = supplier.get();String endTimeStr = LocalDateTime.now().format(DATE_TIME_FORMATTER);// 交给调用者写具体实现,入参供调用者使用logFunc.log(result, startTimeStr, endTimeStr, retryTimes);} catch (Exception e) {e.printStackTrace();} finally {if (result != null && !((BasicResponse<String>) result).getCode().equals(HttpStatus.SC_OK)) {try {Thread.sleep(sleepMillis);} catch (InterruptedException e) {e.printStackTrace();}}retryTimes++;}} while (((result == null) || !((BasicResponse<String>) result).getCode().equals(HttpStatus.SC_OK))&& retryTimes < maxRetryTimes);return result;}
}
测试用例,如下所示,优化前有21行/代码(见第3小节的代码),其实如果不写注释不换行,只需用1行就可以将这个重试机制调用起来了(见下面的代码),简洁多了:
@Slf4j
public class HttpRetryUtilTest extends AppTest {@Resourceprivate HttpRestTemplate restTemplate;@Testpublic void testRetry(){BasicRequest basicRequest = new BasicRequest();basicRequest.setMethodUrl("https://www.google.com");BasicResponse<String> resp = HttpRetryUtil.retryOnException(// 实现supplier函数式接口() -> restTemplate.getForString(basicRequest), // 实现LogFunc函数式接口(response, startTimeStr, endTimeStr, curTime) -> log.info("HttpRetryUtil retryOnException, request:{}, response:{}, startTimeStr:{}, endTimeStr:{}, times:{}", JSON.toJSONString(basicRequest), JSON.toJSONString(response), startTimeStr, endTimeStr, curTime), 3, 500L);log.info("repsonse:{}", JSON.toJSONString(resp));}
}
5 进一步优化
针对那些重试次数、休眠时间,可以在工具类中再定义一些默认的重试次数、默认的休眠时间,然后利用Java的多态特性(方法重载)定义多种工具方法即可。