背景:
在某些查询业务方法内需要用系统预置的一个账户令牌去请求第三方的一个系统,当令牌因为一些原因导致的令牌过期或失效时,为了不直接返回错误信息给客户端,业务代码内需要调用刷新预置账户令牌并重试调用业务方法自身,重新调用第三方系统接口并正确返回数据给客户端,在无需引入SpringRetry等组件时,提供一个简单方法重试工具类。
工具类源码:
package com.msl.tool.retry;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* 方法执行重试器,可用于调用第三方系统异常时进行任务重试
* <br>例举使用场景:
* <p>1.在处理用户请求链路中需要基于第三方系统的授权信息同步调用第三方系统接口当第三方系统令牌失效时,
* 在异常代码块中刷新令牌并重试执行方法,使用新的第三方系统令牌成功请求到数据并响应给客户端,
* 避免客户端第一次请求失败后系统刷新令牌并要求客户端再次重新请求等</p>
*
* @author Zaki Chan
*/
public class RetryUtil {
private static final Logger LOG = LoggerFactory.getLogger(RetryUtil.class);
/**
* 线程内方法重试次数记录
*/
private static final Map<String, Integer> THREAD_METHOD_RETRY_TIMES = new HashMap<>();
private static final Map<String, Integer> THREAD_METHOD_RETRY_DELAY = new HashMap<>();
/**
* 重试执行方法
*
* @param retryTimes 尝试执行的次数
* @param targetInstance 调用此重试方法的方法所在的Class实例对象
* @param args 重试方法的参数
* @return
* @throws NoSuchMethodException
* @throws InvocationTargetException
* @throws IllegalAccessException
*/
public static Object retryCurrentMethod(int retryTimes, Object targetInstance, Object... args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
return doRetryCurrentMethod(retryTimes, 0, targetInstance, args);
}
/**
* 重试执行方法(带延迟)
*
* @param retryTimes 尝试执行的次数
* @param targetInstance 调用此重试方法的方法所在的Class实例对象
* @param retryDelayMillTimeAfterLastFailure 延迟重试(毫秒)
* @param args 重试方法的参数
* @return
* @throws NoSuchMethodException
* @throws InvocationTargetException
* @throws IllegalAccessException
*/
public static Object retryCurrentMethodWithDelay(int retryTimes, Object targetInstance, int retryDelayMillTimeAfterLastFailure, Object... args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
return doRetryCurrentMethod(retryTimes, retryDelayMillTimeAfterLastFailure, targetInstance, args);
}
/**
* 执行方法
*
* @param retryTimes 尝试执行的次数
* @param targetInstance 调用此重试方法的方法所在的Class实例对象
* @param retryDelayMill 延迟重试(毫秒)
* @param args 重试方法的参数
* @return
* @throws NoSuchMethodException
* @throws InvocationTargetException
* @throws IllegalAccessException
*/
private static Object doRetryCurrentMethod(int retryTimes, int retryDelayMill, Object targetInstance, Object... args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
//获取到调用这个执行任务的方法
String methodName = Thread.currentThread().getStackTrace()[3].getMethodName();
//生成当前线程方法重试任务的ID
String key = generateKey(targetInstance, methodName, args);
//记录重试休眠
if (retryDelayMill > 0 && !THREAD_METHOD_RETRY_DELAY.containsKey(key)) {
THREAD_METHOD_RETRY_DELAY.put(key, retryDelayMill);
} else {
retryDelayMill = THREAD_METHOD_RETRY_DELAY.get(key) != null ? THREAD_METHOD_RETRY_DELAY.get(key) : 0;
}
if (retryDelayMill > 0) {
//记下重试休眠
try {
Thread.sleep(retryDelayMill);
} catch (InterruptedException e) {
LOG.error("方法方式延迟执行休眠被打断,任务{}终止执行", key);
e.printStackTrace();
}
}
Integer triedTimes = getRetryTimes(key);
if (triedTimes < retryTimes) {
LOG.info("开始第[{}]次重试任务[{}]", triedTimes, key);
addRetryTimes(key);
Method method;
if (Objects.isNull(args)) {
//无参方法
method = targetInstance.getClass().getDeclaredMethod(methodName);
} else {
//有参方法
Class[] classes = new Class[args.length];
for (int i = 0; i < args.length; i++) {
Object arg = args[i];
Class<?> aClass = arg.getClass();
classes[i] = aClass;
}
method = targetInstance.getClass().getDeclaredMethod(methodName, classes);
}
method.setAccessible(true);
//执行方法
Object invoke = method.invoke(targetInstance, args);
//成功执行,删除本次任务执行次数信息
removeRetryTimes(key);
THREAD_METHOD_RETRY_DELAY.remove(key);
return invoke;
} else {
//重试超次数了
removeRetryTimes(key);
THREAD_METHOD_RETRY_DELAY.remove(key);
LOG.error("任务[{}]重试{}次仍未成功,已取消重试", key, triedTimes);
return null;
}
}
/**
* 基于当前调用方法生成当前调用重试任务的Key
*
* @param targetInstance 调用重试任务的实例类对象
* @param methodName 重试的方法名
* @param args 方法参数
* @return 生成的任务ID
*/
private static String generateKey(Object targetInstance, String methodName, Object... args) {
Thread thread = Thread.currentThread();
long id = thread.getId();
String name = thread.getName();
String className = targetInstance.getClass().getName();
StringBuilder builder = new StringBuilder();
builder.append(className).append("-").append(name).append("-").append(methodName).append("(");
for (int i = 0; i < args.length; i++) {
Object arg = args[i];
String argSimpleName = arg.getClass().getSimpleName();
builder.append(argSimpleName).append(",");
}
builder.append(")").append("-").append(id);
return builder.toString();
}
/**
* 获取已重试次数
*
* @param key 生成的当前调用重试任务Key
* @return
*/
private static Integer getRetryTimes(String key) {
if (!THREAD_METHOD_RETRY_TIMES.containsKey(key)) {
THREAD_METHOD_RETRY_TIMES.put(key, 1);
return 1;
} else {
Integer times = THREAD_METHOD_RETRY_TIMES.get(key);
return times;
}
}
/**
* 删除重试次数信息
*
* @param key 生成的当前调用重试任务Key
* @return
*/
private static void removeRetryTimes(String key) {
THREAD_METHOD_RETRY_TIMES.remove(key);
}
/**
* 累加重试次数
*
* @param key 生成当前调用重试任务Key
*/
private static void addRetryTimes(String key) {
Integer retryTimes = getRetryTimes(key);
int i = retryTimes.intValue() + 1;
THREAD_METHOD_RETRY_TIMES.put(key, i);
}
}
使用伪代码:
Object test(String param1){
//发起第三方系统请求
var status = HttpReq(..) // 发起http请求获取响应结果
if (HttpStatus.HTTP_UNAUTHORIZED == status) { //令牌失效
//刷新令牌
refreshToken();
try {
//重试当前这个方法
RetryUtil.retryCurrentMethod(3, this, param1);
if (Objects.isNull(result)) {
//多次尝试仍然失败...
}
//重试成功 返回结果
return result;
} catch (Exception e) {
log.error("重试查询VTOPS应用列表出错,错误信息:{}", e);
throw new BizException("尝试多次获取VTOPS应用列表错误,请联系管理员");
}
} else{
//成功获取到数据,返回
return ...
}
}