由于某旧项目业务模块需要重构,模块逻辑不复杂,但可能由于之前项目赶工期就写的很复杂,并且掺杂了大量重试代码,于是……重构完模块后,就着手处理这些侵入重试代码。调研发现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 被调用多次的,点赞 + 收藏 一波