From 37c4bb7cc9e0696371bd413c79ad3bc94556c534 Mon Sep 17 00:00:00 2001
From: byteblogs168 <598092184@qq.com>
Date: Tue, 5 Sep 2023 09:29:14 +0800
Subject: [PATCH] =?UTF-8?q?feat:=202.3.0=201.=20=E4=BF=AE=E5=A4=8DRetryabl?=
=?UTF-8?q?e=E4=BD=9C=E7=94=A8=E5=9C=A8=E6=8E=A5=E5=8F=A3=E4=B8=8A?=
=?UTF-8?q?=E6=97=A0=E6=95=88=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../intercepter/EasyRetryInterceptor.java | 225 ++++++++++++++++++
.../intercepter/EasyRetryPointcutAdvisor.java | 181 ++++++++++++++
.../client/core/intercepter/RetryAspect.java | 5 +-
pom.xml | 2 +-
4 files changed, 410 insertions(+), 3 deletions(-)
create mode 100644 easy-retry-client-core/src/main/java/com/aizuda/easy/retry/client/core/intercepter/EasyRetryInterceptor.java
create mode 100644 easy-retry-client-core/src/main/java/com/aizuda/easy/retry/client/core/intercepter/EasyRetryPointcutAdvisor.java
diff --git a/easy-retry-client-core/src/main/java/com/aizuda/easy/retry/client/core/intercepter/EasyRetryInterceptor.java b/easy-retry-client-core/src/main/java/com/aizuda/easy/retry/client/core/intercepter/EasyRetryInterceptor.java
new file mode 100644
index 00000000..18111ce8
--- /dev/null
+++ b/easy-retry-client-core/src/main/java/com/aizuda/easy/retry/client/core/intercepter/EasyRetryInterceptor.java
@@ -0,0 +1,225 @@
+package com.aizuda.easy.retry.client.core.intercepter;
+
+import cn.hutool.core.util.StrUtil;
+import com.aizuda.easy.retry.client.core.annotation.Retryable;
+import com.aizuda.easy.retry.client.core.cache.GroupVersionCache;
+import com.aizuda.easy.retry.client.core.config.EasyRetryProperties;
+import com.aizuda.easy.retry.client.core.intercepter.RetrySiteSnapshot.EnumStage;
+import com.aizuda.easy.retry.client.core.retryer.RetryerResultContext;
+import com.aizuda.easy.retry.client.core.strategy.RetryStrategy;
+import com.aizuda.easy.retry.common.core.alarm.Alarm;
+import com.aizuda.easy.retry.common.core.alarm.AlarmContext;
+import com.aizuda.easy.retry.common.core.alarm.EasyRetryAlarmFactory;
+import com.aizuda.easy.retry.common.core.enums.NotifySceneEnum;
+import com.aizuda.easy.retry.common.core.enums.RetryResultStatusEnum;
+import com.aizuda.easy.retry.common.core.log.LogUtils;
+import com.aizuda.easy.retry.common.core.util.EnvironmentUtils;
+import com.aizuda.easy.retry.server.model.dto.ConfigDTO;
+import com.google.common.base.Defaults;
+import lombok.extern.slf4j.Slf4j;
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+import org.springframework.aop.AfterAdvice;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.core.env.StandardEnvironment;
+import org.springframework.stereotype.Component;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Objects;
+import java.util.UUID;
+
+/**
+ * @author www.byteblogs.com
+ * @date 2023-08-23
+ */
+@Slf4j
+@Component
+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 =
+ "{}环境 重试组件异常 \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 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);
+ }
+
+}
diff --git a/easy-retry-client-core/src/main/java/com/aizuda/easy/retry/client/core/intercepter/EasyRetryPointcutAdvisor.java b/easy-retry-client-core/src/main/java/com/aizuda/easy/retry/client/core/intercepter/EasyRetryPointcutAdvisor.java
new file mode 100644
index 00000000..84edd771
--- /dev/null
+++ b/easy-retry-client-core/src/main/java/com/aizuda/easy/retry/client/core/intercepter/EasyRetryPointcutAdvisor.java
@@ -0,0 +1,181 @@
+package com.aizuda.easy.retry.client.core.intercepter;
+
+import com.aizuda.easy.retry.client.core.annotation.Retryable;
+import org.aopalliance.aop.Advice;
+import org.springframework.aop.ClassFilter;
+import org.springframework.aop.IntroductionAdvisor;
+import org.springframework.aop.MethodMatcher;
+import org.springframework.aop.Pointcut;
+import org.springframework.aop.support.AbstractPointcutAdvisor;
+import org.springframework.aop.support.ComposablePointcut;
+import org.springframework.aop.support.StaticMethodMatcherPointcut;
+import org.springframework.aop.support.annotation.AnnotationClassFilter;
+import org.springframework.aop.support.annotation.AnnotationMethodMatcher;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.BeanFactoryAware;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.stereotype.Component;
+import org.springframework.util.ObjectUtils;
+import org.springframework.util.ReflectionUtils;
+import org.springframework.util.ReflectionUtils.MethodCallback;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * @author www.byteblogs.com
+ * @date 2023-08-23
+ */
+@Component
+public 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> retryableAnnotationTypes = new LinkedHashSet>(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> 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, 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();
+ }
+
+ }
+
+
+
+}
diff --git a/easy-retry-client-core/src/main/java/com/aizuda/easy/retry/client/core/intercepter/RetryAspect.java b/easy-retry-client-core/src/main/java/com/aizuda/easy/retry/client/core/intercepter/RetryAspect.java
index c288503b..597bddc9 100644
--- a/easy-retry-client-core/src/main/java/com/aizuda/easy/retry/client/core/intercepter/RetryAspect.java
+++ b/easy-retry-client-core/src/main/java/com/aizuda/easy/retry/client/core/intercepter/RetryAspect.java
@@ -44,8 +44,9 @@ import java.util.UUID;
* @author: www.byteblogs.com
* @date : 2022-03-03 11:41
*/
-@Aspect
-@Component
+//@Aspect
+//@Component
+@Deprecated
@Slf4j
public class RetryAspect implements Ordered {
diff --git a/pom.xml b/pom.xml
index 2372249d..2939561d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,7 +21,7 @@
1.8
1.8
1.8
- 2.2.0
+ 2.3.0-SNAPSHOT
1.0.0
4.1.94.Final
5.8.19