前言:
此刻各位老铁们对“java调用接口异常重试”大体比较关注,大家都需要了解一些“java调用接口异常重试”的相关内容。那么小编同时在网上收集了一些对于“java调用接口异常重试””的相关文章,希望大家能喜欢,同学们一起来了解一下吧!背景
最近有一个小伙伴提了一个issues 指出@Retryable注解在接口上不生效 · Issue #I7VGS8 · aizuda/easy-retry - Gitee.com 首先我们复现issues问题.
问题复现
1. 新建一个接口并添加@Retryable注解
public interface LocalRetryService { @Retryable(scene = "localRetryWithAnnoOnInterface", retryStrategy = RetryType.ONLY_LOCAL) void localRetryWithAnnoOnInterface(String params);}
实现接口并执行一个异常的代码
@Componentpublic class LocalRetryServiceImpl implements LocalRetryService { @Override public void localRetryWithAnnoOnInterface(final String params) { double i = 1 / 0; }}
2. 观察日志是否触发异常
通过观察日志并未触发重试
> 想要知道为什么会出现这个问题就得了解一下啊注解的继承问题?
# 注解的继承问题
经过测试得出以下结论
那为啥注解在接口上没作用?
Spring 的动态代理主要分为两种,一种是JDK 动态代理 ;一种是CGLIB 动态代理;
JDK 动态代理
JDK 动态代理主要是针对实现了某个接口的类。该方式基于反射的机制实现,会生成一个实现相同接口的代理类,然后通过对方法的充写,实现对代码的增强。
在该方式中接口中的注解无法被实现类继承,AOP 中的切点无法匹配上实现类,所以也就不会为实现类创建代理,所以我们使用的类其实是未被代理的原始类,自然也就不会被增强了。
CGLIB 动态代理
1. 不存在继承关系 AOP可进行有效拦截(CGLIB动态代理)
2. 存在继承关系 有父类和子类 ,切点注解在父类方法。若子类重写父类的方法将不会被拦截,而未重写的方法可以被AOP拦截。
解决方案
我们知道事务的注解@Transactional和Spring Retry的注解@Retryable都是支持在接口的方法和抽象类的方法上,不妨先学习一下他们是如何实现
先阅读一下Spring Retry的关于这块的源码
public class RetryConfiguration extends AbstractPointcutAdvisor implements IntroductionAdvisor, BeanFactoryAware { ... private final class AnnotationClassOrMethodFilter extends AnnotationClassFilter { private final AnnotationMethodsResolver methodResolver; AnnotationClassOrMethodFilter(Class<? extends Annotation> annotationType) { super(annotationType, true); this.methodResolver = new AnnotationMethodsResolver(annotationType); } @Override public boolean matches(Class<?> clazz) { return super.matches(clazz) || this.methodResolver.hasAnnotatedMethods(clazz); } } private static class AnnotationMethodsResolver { private Class<? extends Annotation> annotationType; public AnnotationMethodsResolver(Class<? extends Annotation> annotationType) { this.annotationType = annotationType; } public boolean hasAnnotatedMethods(Class<?> clazz) { final AtomicBoolean found = new AtomicBoolean(false); ReflectionUtils.doWithMethods(clazz, new MethodCallback() { @Override public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { if (found.get()) { return; } Annotation annotation = AnnotationUtils.findAnnotation(method, annotationType); if (annotation != null) { found.set(true); } } }); return found.get(); } } }
原来如此,他们是自动生成一个增强类,通过Annotation annotation = AnnotationUtils.findAnnotation(method, annotationType);和super.matches(clazz)配置类和方法上注解,说到这里我们知道原理,下面按照这种方式实现我们自己的增强器和拦截器
仿造Spring Retry实现对接口的注解进行拦截新增EasyRetryPointcutAdvisor增强器
/** * @author * @date 2023-08-23 *///@Componentpublic class EasyRetryPointcutAdvisor extends AbstractPointcutAdvisor implements IntroductionAdvisor, BeanFactoryAware, InitializingBean { private Advice advice; private Pointcut pointcut; private BeanFactory beanFactory; @Autowired private EasyRetryInterceptor easyRetryInterceptor; @Override public void afterPropertiesSet() throws Exception { Set<Class<? extends Annotation>> retryableAnnotationTypes = new LinkedHashSet<Class<? extends Annotation>>(1); retryableAnnotationTypes.add(Retryable.class); this.pointcut = buildPointcut(retryableAnnotationTypes); this.advice = buildAdvice(); if (this.advice instanceof BeanFactoryAware) { ((BeanFactoryAware) this.advice).setBeanFactory(beanFactory); } } /** * Set the {@code BeanFactory} to be used when looking up executors by qualifier. */ @Override public void setBeanFactory(BeanFactory beanFactory) { this.beanFactory = beanFactory; } @Override public ClassFilter getClassFilter() { return pointcut.getClassFilter(); } @Override public Class<?>[] getInterfaces() { return new Class[] { Retryable.class }; } @Override public void validateInterfaces() throws IllegalArgumentException { } @Override public Advice getAdvice() { return this.advice; } protected Advice buildAdvice() { return easyRetryInterceptor; } /** * Calculate a pointcut for the given retry annotation types, if any. * @param retryAnnotationTypes the retry annotation types to introspect * @return the applicable Pointcut object, or {@code null} if none */ protected Pointcut buildPointcut(Set<Class<? extends Annotation>> retryAnnotationTypes) { ComposablePointcut result = null; for (Class<? extends Annotation> retryAnnotationType : retryAnnotationTypes) { Pointcut filter = new AnnotationClassOrMethodPointcut(retryAnnotationType); if (result == null) { result = new ComposablePointcut(filter); } else { result.union(filter); } } return result; } @Override public Pointcut getPointcut() { return pointcut; } private final class AnnotationClassOrMethodPointcut extends StaticMethodMatcherPointcut { private final MethodMatcher methodResolver; AnnotationClassOrMethodPointcut(Class<? extends Annotation> annotationType) { this.methodResolver = new AnnotationMethodMatcher(annotationType); setClassFilter(new AnnotationClassOrMethodFilter(annotationType)); } @Override public boolean matches(Method method, Class<?> targetClass) { return getClassFilter().matches(targetClass) || this.methodResolver.matches(method, targetClass); } @Override public boolean equals(Object other) { if (this == other) { return true; } if (!(other instanceof AnnotationClassOrMethodPointcut)) { return false; } AnnotationClassOrMethodPointcut otherAdvisor = (AnnotationClassOrMethodPointcut) other; return ObjectUtils.nullSafeEquals(this.methodResolver, otherAdvisor.methodResolver); } } private final class AnnotationClassOrMethodFilter extends AnnotationClassFilter { private final AnnotationMethodsResolver methodResolver; AnnotationClassOrMethodFilter(Class<? extends Annotation> annotationType) { super(annotationType, true); this.methodResolver = new AnnotationMethodsResolver(annotationType); } @Override public boolean matches(Class<?> clazz) { return super.matches(clazz) || this.methodResolver.hasAnnotatedMethods(clazz); } } private static class AnnotationMethodsResolver { private Class<? extends Annotation> annotationType; public AnnotationMethodsResolver(Class<? extends Annotation> annotationType) { this.annotationType = annotationType; } public boolean hasAnnotatedMethods(Class<?> clazz) { final AtomicBoolean found = new AtomicBoolean(false); ReflectionUtils.doWithMethods(clazz, method -> { if (found.get()) { return; } Annotation annotation = AnnotationUtils.findAnnotation(method, annotationType); if (annotation != null) { found.set(true); } }); return found.get(); } }}实现拦截器[EasyRetryInterceptor]()
public class EasyRetryInterceptor implements MethodInterceptor, AfterAdvice, Serializable, Ordered { private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); private static String retryErrorMoreThresholdTextMessageFormatter = "<font face=\"微软雅黑\" color=#ff0000 size=4>{}环境 重试组件异常</font> \r\n" + "> 名称:{} \r\n" + "> 时间:{} \r\n" + "> 异常:{} \n" ; @Autowired @Qualifier("localRetryStrategies") private RetryStrategy retryStrategy; @Autowired private EasyRetryAlarmFactory easyRetryAlarmFactory; @Autowired private StandardEnvironment standardEnvironment; @Override public Object invoke(MethodInvocation invocation) throws Throwable { String traceId = UUID.randomUUID().toString(); LogUtils.debug(log,"Start entering the around method traceId:[{}]", traceId); Retryable retryable = getAnnotationParameter(invocation.getMethod()); String executorClassName = invocation.getThis().getClass().getName(); String methodEntrance = getMethodEntrance(retryable, executorClassName); if (StrUtil.isBlank(RetrySiteSnapshot.getMethodEntrance())) { RetrySiteSnapshot.setMethodEntrance(methodEntrance); } Throwable throwable = null; Object result = null; RetryerResultContext retryerResultContext; try { result = invocation.proceed(); } catch (Throwable t) { throwable = t; } finally { LogUtils.debug(log,"Start retrying. traceId:[{}] scene:[{}] executorClassName:[{}]", traceId, retryable.scene(), executorClassName); // 入口则开始处理重试 retryerResultContext = doHandlerRetry(invocation, traceId, retryable, executorClassName, methodEntrance, throwable); } LogUtils.debug(log,"Method return value is [{}]. traceId:[{}]", result, traceId, throwable); // 若是重试完成了, 则判断是否返回重试完成后的数据 if (Objects.nonNull(retryerResultContext)) { // 重试成功直接返回结果 若注解配置了isThrowException=false 则不抛出异常 if (retryerResultContext.getRetryResultStatusEnum().getStatus().equals(RetryResultStatusEnum.SUCCESS.getStatus()) || !retryable.isThrowException()) { // 若返回值是NULL且是基本类型则返回默认值 Method method = invocation.getMethod(); if (Objects.isNull(retryerResultContext.getResult()) && method.getReturnType().isPrimitive()) { return Defaults.defaultValue(method.getReturnType()); } return retryerResultContext.getResult(); } } if (throwable != null) { throw throwable; } else { return result; } } private RetryerResultContext doHandlerRetry(MethodInvocation invocation, String traceId, Retryable retryable, String executorClassName, String methodEntrance, Throwable throwable) { if (!RetrySiteSnapshot.isMethodEntrance(methodEntrance) || RetrySiteSnapshot.isRunning() || Objects.isNull(throwable) // 重试流量不开启重试 || RetrySiteSnapshot.isRetryFlow() // 下游响应不重试码,不开启重试 || RetrySiteSnapshot.isRetryForStatusCode() ) { if (!RetrySiteSnapshot.isMethodEntrance(methodEntrance)) { LogUtils.debug(log, "Non-method entry does not enable local retries. traceId:[{}] [{}]", traceId, RetrySiteSnapshot.getMethodEntrance()); } else if (RetrySiteSnapshot.isRunning()) { LogUtils.debug(log, "Existing running retry tasks do not enable local retries. traceId:[{}] [{}]", traceId, EnumStage.valueOfStage(RetrySiteSnapshot.getStage())); } else if (Objects.isNull(throwable)) { LogUtils.debug(log, "No exception, no local retries. traceId:[{}]", traceId); } else if (RetrySiteSnapshot.isRetryFlow()) { LogUtils.debug(log, "Retry traffic does not enable local retries. traceId:[{}] [{}]", traceId, RetrySiteSnapshot.getRetryHeader()); } else if (RetrySiteSnapshot.isRetryForStatusCode()) { LogUtils.debug(log, "Existing exception retry codes do not enable local retries. traceId:[{}]", traceId); } else { LogUtils.debug(log, "Unknown situations do not enable local retry scenarios. traceId:[{}]", traceId); } return null; } return openRetry(invocation, traceId, retryable, executorClassName, throwable); } private RetryerResultContext openRetry(MethodInvocation point, String traceId, Retryable retryable, String executorClassName, Throwable throwable) { try { RetryerResultContext context = retryStrategy.openRetry(retryable.scene(), executorClassName, point.getArguments()); LogUtils.info(log,"local retry result. traceId:[{}] message:[{}]", traceId, context); if (RetryResultStatusEnum.SUCCESS.getStatus().equals(context.getRetryResultStatusEnum().getStatus())) { LogUtils.debug(log, "local retry successful. traceId:[{}] result:[{}]", traceId, context.getResult()); } return context; } catch (Exception e) { LogUtils.error(log,"retry component handling exception,traceId:[{}]", traceId, e); // 预警 sendMessage(e); } finally { RetrySiteSnapshot.removeAll(); } return null; } private void sendMessage(Exception e) { try { ConfigDTO.Notify notifyAttribute = GroupVersionCache.getNotifyAttribute(NotifySceneEnum.CLIENT_COMPONENT_ERROR.getNotifyScene()); if (Objects.nonNull(notifyAttribute)) { AlarmContext context = AlarmContext.build() .text(retryErrorMoreThresholdTextMessageFormatter, EnvironmentUtils.getActiveProfile(), EasyRetryProperties.getGroup(), LocalDateTime.now().format(formatter), e.getMessage()) .title("retry component handling exception:[{}]", EasyRetryProperties.getGroup()) .notifyAttribute(notifyAttribute.getNotifyAttribute()); Alarm<AlarmContext> alarmType = easyRetryAlarmFactory.getAlarmType(notifyAttribute.getNotifyType()); alarmType.asyncSendMessage(context); } } catch (Exception e1) { LogUtils.error(log, "Client failed to send component exception alert.", e1); } } public String getMethodEntrance(Retryable retryable, String executorClassName) { if (Objects.isNull(retryable)) { return StrUtil.EMPTY; } return retryable.scene().concat("_").concat(executorClassName); } private Retryable getAnnotationParameter(Method method) { Retryable retryable = null; if (method.isAnnotationPresent(Retryable.class)) { //获取当前类的方法上标注的注解对象 retryable = method.getAnnotation(Retryable.class); } if (retryable == null) { //返回当前类或父类或接口方法上标注的注解对象 retryable = AnnotatedElementUtils.findMergedAnnotation(method, Retryable.class); }// if (retryable == null) {// //返回当前类或父类或接口上标注的注解对象// retryable = AnnotatedElementUtils.findMergedAnnotation(method.getDeclaringClass(), Retryable.class);// } return retryable; } @Override public int getOrder() { String order = standardEnvironment .getProperty("easy-retry.aop.order", String.valueOf(Ordered.HIGHEST_PRECEDENCE)); return Integer.parseInt(order); }}
## 测试改造结果
> 从测试结果来看,效果还是很不错了,完美的解决了这个问题
一波小广告
EasyRetry是一款基于BASE思想实现的分布式服务重试组件,旨在通过重试机制确保数据的最终一致性。它提供了控制台任务观测、可配置的重试策略、重试后执行回调以及丰富地告警配置等功能。通过这些手段,可以对异常数据进行全面监测和回放,从而在确保系统高可用性的同时,大大提升数据的一致性
为了便于快速上手EasyRetry特别的录制了视频教程还在持续的录制中有兴趣可以看看。
视频地址:
开源不易,路过的小伙伴点点Star:
标签: #java调用接口异常重试