java自定义注解,实现方法重试,支持自定义重试策略

        这两天在做项目过程中,需要请求外部服务的api需要考虑重试,为了代码优雅,决定使用注解的方式进行重试的配置,手写了个注解,

支持配置:重试的次数

支持自定义策略:1、延迟重试、即每失败一次下次重试延迟

                             2、根据配置的错误码code开启重试,不传默认所有失败都重试

                             3、redis的策略配置,方法失败,删除特定的redis

非常好用,有问题可以提问,看到会回复,删除了公司业务代码,下面业务逻辑使用了伪代码,方便理解

废话不多说,上代码:

首先是三个需要配置的注解:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author hanfeng
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RetryableProcess {
    /**
     * 失败重试次数
     * @return
     */
    int maxAttempts() default 3;

    /**
     * 重试策略、操作
     * @return
     */
    BackoffProcess backoff() default @BackoffProcess;

}

策略配置注解:


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author hanfeng
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface BackoffProcess {

    /**
     * 初始重试间隔毫秒数
     * @return
     */
    long value() default 0L;

    /**
     * 重试最大延迟
     * @return
     */
    long maxDelay() default 0L;

    /**
     * 重试乘数
     * @return
     */
    double multiplier() default 0.0D;

    /**
     * 根据失败错误码code开启重试,不传默认失败执行
     * @return
     */
    String[] retryExceptionCode() default "";

    /**
     * redis策略
     * @return
     */
    RetryableRedisProcess redisOperation() default @RetryableRedisProcess;

}

redis策略注解:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author hanfeng
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface  RetryableRedisProcess {

    /**
     * 重试时删除的redis
     * @return
     */
    String[] retryRedisRemove() default {};
}

切面文件:



import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;

/**
 * @author hanfeng
 */
@Component
@Aspect
public class RetryProcessAspect {
    protected org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(this.getClass());
    // 配置RetryableProcess.class路径
    @Pointcut("@annotation(com.test.aop.RetryableProcess)")
    public void pointCutR() {
    }

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 埋点拦截器具体实现
     */
    @Around("pointCutR()")
    public Object methodRHandler(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method targetMethod = methodSignature.getMethod();
        RetryableProcess retryableProcess = targetMethod.getAnnotation(RetryableProcess.class);
        BackoffProcess backoff = retryableProcess.backoff();
        RetryableRedisProcess redisOperation = backoff.redisOperation();
        int maxAttempts = retryableProcess.maxAttempts();
        long sleepSecond = backoff.value();
        double multiplier = backoff.multiplier();
        if (multiplier <= 0) {
            multiplier = 1;
        }
        Exception ex = null;
        int retryCount = 0;
        do {
            try {

                Object proceed = joinPoint.proceed();
                return proceed;
            } catch (BtException e) {
                logger.info("等待{}毫秒", sleepSecond);
                Thread.sleep(sleepSecond);
                retryCount++;
                sleepSecond = (long) (multiplier) * sleepSecond;
                if (sleepSecond > backoff.maxDelay()) {
                    sleepSecond = backoff.maxDelay();
                    logger.info("等待时间太长,更新为{}毫秒", sleepSecond);
                }

                List<String> strings = Arrays.asList(backoff.retryExceptionCode());
                if (backoff.retryExceptionCode().length > 0 && !strings.contains(e.getErrorEnum().getCode())) {
                    throw e;
                }
                ex = e;
                redisOperation(redisOperation);
            }
        } while (retryCount <= maxAttempts);

        throw ex;
    }

    /**
     * redis操作
     * @param redisOperation
     */
    public void redisOperation(RetryableRedisProcess redisOperation){
        String[] redisRemoves = redisOperation.retryRedisRemove();
        for (String redisName : redisRemoves) {
            redisTemplate.delete(redisName);
        }

    }



}

因为上面用到了错误码,所以把封装的异常类也贴下:


/**
 * 
 * @author hanfeng
 *
 */
public class BtException extends RuntimeException {


	private String code;
	private IErrorEnum errorEnum;

	@SuppressWarnings("unused")
	private BtException() {
	}

	public void setCode(String code) {
		this.code = code;
	}

	public void setErrorEnum(IErrorEnum errorEnum) {
		this.errorEnum = errorEnum;
	}

	protected boolean canEqual(Object other) {
		return other instanceof BtException;
	}

	@Override
	public String toString() {
		return "BusinessException(code=" + getCode() + ", errorEnum=" + getErrorEnum() + ")";
	}

	public String getCode() {
		return this.code;
	}

	public IErrorEnum getErrorEnum() {
		return this.errorEnum;
	}

	public BtException(IErrorEnum errorEnum) {
		super(errorEnum.getDescription());
		this.errorEnum = errorEnum;
		this.code = errorEnum.getCode();
	}

	public BtException(String code, String message) {
		super(message);
		this.code = code;
	}

}

异常枚举类基类,可以创建枚举实现IErrorEnum 类,来配置错误码,也可以改代码用自己的错误码来实现,对错误码的控制

/**
 * 异常枚举基类
 * @author hanfeng
 *
 */
public interface IErrorEnum {
	String getCode();

	String getDescription();

	default String codeMsg() {
		return getCode()+" "+ getDescription();
	}
}

注解的使用:

    // 第三方token失效请求错误code
    static final String HTTP_TOKEN_FAIL = "000015";
    // 第三方超时请求错误code
    static final String REQUEST_TIME_OUT = "000016";
    //删除redis key
    static final String remove_redis_key = "ssss:redis:key";


    @RetryableProcess(maxAttempts = 2, backoff = @BackoffProcess(retryExceptionCode = {HTTP_TOKEN_FAIL, REQUEST_TIME_OUT}, redisOperation = @RetryableRedisProcess(retryRedisRemove = {remove_redis_key})))
    public void test1(){
        // ...请求第三方代码忽略
        if(第三方失败code为token失效){
            throw new BtException(ErrorEnum.HTTP_TOKEN_FAIL);
        }else if(网络请求超时){
            throw new BtException(ErrorEnum.REQUEST_TIME_OUT);
        }

    }

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值