由于某旧项目业务模块需要重构,模块逻辑不复杂,但可能由于之前项目赶工期就写的很复杂,并且掺杂了大量重试代码,于是……重构完模块后,就着手处理这些侵入重试代码。调研发现jfinal的拦截器目前不支持做末端免侵入重试,于是……
1. 修改JFinal的Invocation.class以支持末端(方法调用)重试功能(如果是处理拦截器调用链的异常重试,jfinal默认就支持)
public class Invocation {
// ......
public void invoke() {
if (index < inters.length) {
inters[index++].intercept(this);
}
// 修改的地方1。原为:else if (index++ == inters.length) {
else if (index == inters.length) {
try {
// Invoke the action
if (action != null) {
returnValue = action.getMethod().invoke(target, args);
}
// Invoke the callback
else {
returnValue = callback.call(args);
}
// 修改的地方2:将index++移动到此处,便于支持异常重试
index++;
}
catch (InvocationTargetException e) {
Throwable t = e.getTargetException();
if (t == null) {t = e;}
throw t instanceof RuntimeException ? (RuntimeException)t : new RuntimeException(t);
}
catch (RuntimeException e) {
throw e;
}
catch (Throwable t) {
throw new RuntimeException(t);
}
}
}
// ......
}2. 添加Retry注解,如果有需求可以加字段,以支持重试前后做特殊处理等。
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Retry {
/**
* 重试次数
* @return 重试次数
*/
int times() default 1;
/**
* 重试时间间隔,毫秒
* @return 重试时间间隔
*/
int timeInterval() default 0;
/**
* 需要处理的异常类型
* @return 异常类型
*/
Class<? extends Exception>[] exception();
}3. 添加RetryInterceptor拦截器
public class RetryInterceptor implements Interceptor {
private final Log logger = Log.getLog(RetryInterceptor.class);
@Override
public void intercept(Invocation inv) {
final Retry retry = inv.getMethod().getAnnotation(Retry.class);
// 不需要重试、重试次数小于1或未设置重试条件(需重试处理的异常)
if (null == retry || retry.times() < 1 || 0 == retry.exception().length) {
inv.invoke();
return;
}
// 重试处理逻辑
for (int i = 0; i <= retry.times(); i++) {
try {
inv.invoke();
} catch (Exception e) {
// 最后一次重试抛出异常,则不处理
if (i == retry.times()) {
throw e;
}
// 不在要处理的异常之列,也不处理
if (Arrays.stream(retry.exception())
.noneMatch(item -> item.isInstance(e) || Arrays.stream(e.getSuppressed()).anyMatch(item::isInstance))) {
throw e;
}
if (i > 0 && logger.isDebugEnabled()) {
logger.debug("[retry][%s.%s()] %d"
.formatted(inv.getTarget().getClass().getName(), inv.getMethodName(), i), e);
}
}
}
}
}4. controller层测试及service层测试
@Path(value = "/test")
public class TestController extends Controller {
private final AtomicInteger atomicInteger = new AtomicInteger(0);
@Retry(times = 3, exception = RuntimeException.class)
@Before(RetryInterceptor.class)
public String retry() {
final int i = atomicInteger.incrementAndGet();
if (i % 4 != 0) {
throw new RuntimeException("test: " + i);
}
return "Hello World! ";
}
}由于个人比较菜,代码多有不足,请大佬们多多指教哈!
经过这个改造,去除老项目代码中的重试代码,简单、清爽
Invocation 中 index++ 的改动,证明对 jfinal 核心有深刻把控,这个地方确实是为了防止 action 被调用多次的,点赞 + 收藏 一波