栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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;


@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RetryableProcess {
    
    int maxAttempts() default 3;

    
    BackoffProcess backoff() default @BackoffProcess;

}

策略配置注解:

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


@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface BackoffProcess {

    
    long value() default 0L;

    
    long maxDelay() default 0L;

    
    double multiplier() default 0.0D;

    
    String[] retryExceptionCode() default "";

    
    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;


@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface  RetryableRedisProcess {

    
    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;


@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 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;
    }

    
    public void redisOperation(RetryableRedisProcess redisOperation){
        String[] redisRemoves = redisOperation.retryRedisRemove();
        for (String redisName : redisRemoves) {
            redisTemplate.delete(redisName);
        }

    }



}

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


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 类,来配置错误码,也可以改代码用自己的错误码来实现,对错误码的控制

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);
        }

    }

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/344764.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号