业务中经常使用Http调用接口,由于网络抖动,时常会出现调用失败、连接超时、读取超时、远程服务器关闭连接等情况。因此会在使用HTTP调接口的地方加上重试逻辑。这就导致了很多业务逻辑代码都有重试逻辑,代码不够简洁,可重用性也不高。因此需要进一步优化代码,将重试逻辑抽成一个工具类(静态方法)。
重试逻辑的伪如下:
CaptureOrderResponse resp = null;
int retryTimes = 0;
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
do {
try {
String startTimeStr = formatter.format(LocalDateTime.now());
resp = execute(null, header, null, restTemplate, CaptureOrderResponse.class, payRouter); // 1
String endTimeStr = formatter.format(LocalDateTime.now());
log.info("business log info, response:{}, startTimeStr:{}, endTimeStr:{}, retryTimes:{}", JSON.toJSONString(resp), startTimeStr, endTimeStr, retryTimes);
} finally {
if (resp != null && !resp.getCode().equals(HttpStatus.SC_OK)) {
try {
Thread.sleep(500L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
retryTimes++;
}
} while ((Objects.isNull(resp) || !resp.getCode().equals(HttpStatus.SC_OK)) && retryTimes < 3);
分析:除了上面代码块中标记了1
的那行代码(业务逻辑的代码,需要重试的代码)以及一些HTTP响应实体类,其余代码基本都是公用逻辑,所以可以将公用逻辑抽取到工具方法里面,会变的标记1
所在的代码以及响应实体类交给静态方法的入参以及函数式编程实现。
定义可变实体类的接口方法,使得只要继承了该类,无论什么响应实体类都可以在工具类方法中调用接口方法:
@Data
public class PayBaseResponse {
private Integer code;
private String responseMateData;
private String errorMsg;
private String requestMateData;
}
定义3个函数式接口
Invoker
、LogAfterInvoker
、LogBeforeInvoker
,下面是伪代码:
public class HttpRetryUtil {
public static <T extends PayBaseResponse> T invokeTilSpecStatus(Invoker<T> invoker,
Object requestData,
LogBeforeInvoker logBeforeInvoker,
LogAfterInvoker logAfterInvoker,
int retryTimes,
Long sleepInterval,
TimeUnit timeUnit,
int httpStatus
) throws Exception {
Objects.requireNonNull(invoker, "Invoker must not null!");
Objects.requireNonNull(timeUnit, "TimeUnit must not null!");
Assert.assertTrue("The maximum number of retries cannot exceed 3 times", retryTimes <= MAX_RETRY_TIMES);
Assert.assertTrue("The minimum number of retries cannot be less than 1", retryTimes >= MIN_RETRY_TIMES);
long sleepMillis = timeUnit.toMillis(sleepInterval);
T resp = null;
int currentTimes = 0;
do {
try {
if (Objects.nonNull(logBeforeInvoker)) {
logBeforeInvoker.logContent();
}
String startTimeStr = DATE_TIME_FORMATTER.format(LocalDateTime.now());
resp = invoker.invoke();
if (Objects.nonNull(logAfterInvoker)) {
logAfterInvoker.logContent();
}
String endTimeStr = DATE_TIME_FORMATTER.format(LocalDateTime.now());
log.info("HttpRetryUtil invokeTilSpecStatus, request:{}, response:{}, startTimeStr:{}, endTimeStr:{}, currentTimes:{}", JSON.toJSONString(requestData), JSON.toJSONString(resp), startTimeStr, endTimeStr, currentTimes);
} finally {
if (resp != null && !resp.getCode().equals(httpStatus)) {
try {
Thread.sleep(sleepMillis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
currentTimes++;
}
} while ((Objects.isNull(resp) || !resp.getCode().equals(HttpStatus.SC_OK)) && currentTimes < retryTimes);
return resp;
}
/**
* 函数式接口
* @param
*/
@FunctionalInterface
public interface Invoker<T extends PayBaseResponse> {
T invoke() throws Exception;
}
/**
* 执行方法后打印日志
*
*/
@FunctionalInterface
public interface LogAfterInvoker {
void logContent();
}
/**
* 执行方法前打印日志
*/
@FunctionalInterface
public interface LogBeforeInvoker {
void logContent();
}
}
用法:如下所示,其中用到的
()-> xxx
就是函数式作为入参
public static void main(String[] args) {
try {
response = HttpRetryUtil.invokeTilSpecStatus(
() -> execute(deliverRequest, header, null, restTemplate, PayBaseResponse.class, payRouter),
deliverRequest,
() -> log.info("log content before invoke"),
() -> log.info("log content after invoke"),
3,
500L,
TimeUnit.MILLISECONDS,
HttpStatus.SC_OK
);
log.info("Business, request:{}, response:{}", JSON.toJSONString(deliverRequest), JSON.toJSONString(response));
} catch (Exception e) {
log.error("Business error, exception:", e);
}
}