springboot下用aop实现异步调用和重试功能

我们在开发中,调用第三方接口时,往往是提交数据,要异步去获取数据;今天我们用一个利用spring的安排来实现一下异步调用和异步重试的功能;

这个功能的结构图

springboot下用aop实现异步调用和重试功能

第一步:创建一个注解Retry

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Retry {

     /**
      * 延时执行时间  (单位:秒)
      * @return
      */
     long delayed() default 0;

     /**
      * 重复执行间隔时间  (单位:秒)
      * @return
      */
     long interval() default 0;

     /**
      * 重复的次数  最大10次
      * @return
      */
     int retryTimes() default 1;

}

创建类:

RetryInvorkParam 类 ;用于记录重复执行的一些参数
/**
 * 重试执行参数
 * @author chenhuaping
 * @date  2018年4月20日 下午2:54:39
 */
@Setter
@Getter
@ToString
public class RetryInvorkParam implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 延迟时间
     */
    private long delayed;

    /**
     * 间隔时间
     */
    private long interval;

    /**
     * 重复次数
     */
    private int retryTimes;

    /**
     * 执行的方法的对象
     */
    private Object target;

    /**
     * 执行的参数
     */
    private Object[] args;

    /**
     * 执行的方法
     */
    private Method invorkMethod;

    /**
     * 当前执行的次数
     */
    private volatile long currentTimes;

    /**
     * 下一次执行的时间
     */
    private volatile Date nextInvorkTime;

    /**
     * 是否结束这重试
     */
    private volatile boolean isEnd;

}

创建异步执行的时候的任务:

/**
 * 方法执行任务
 *
 * @author chenhuaping
 * @date 2018年4月20日 下午3:54:13
 */
public class RetryRunning implements Runnable {

    private final static Logger logger = LoggerFactory.getLogger(RetryRunning.class);
    /**
     * 执行的参数
     */
    private RetryInvorkParam invorkParam;

    public RetryRunning(RetryInvorkParam invorkParam) {
        super();
        this.invorkParam = invorkParam;
    }

    @Override
    public void run() {

        Method invorkMethod = invorkParam.getInvorkMethod();
        Object[] args = invorkParam.getArgs();
        Object target = invorkParam.getTarget();

        Object result = null;

        try {
            result = invorkMethod.invoke(target, args);
        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
            logger.error("@Retry 执行方法错误, {}", invorkParam, e);
            invorkParam.setEnd(true);
        }

        if (result != null && result instanceof Boolean && (boolean) result == true) {
            invorkParam.setEnd(true);
        }

        if (invorkParam.isEnd()) {
            logger.info("@Retry method = {}, 重复执行了{}次,已经执行结束,param = {}",
                    target.getClass() + "." + invorkMethod.getName(), invorkParam.getCurrentTimes(), invorkParam);
            return;
        }

        logger.info("@Retry 已经完成了{}方法,第{}次执行,总共需要执行{}次", target.getClass() + "." + invorkMethod.getName(),
                invorkParam.getCurrentTimes(), invorkParam.getRetryTimes());

    }

}

创建一个定时任务;每一秒去轮询一次重试任务列表:并把已经到时间的任务提交到线程池中:

/**
 * 线程提交执行引擎
 *
 * @author chenhuaping
 * @date 2018年4月20日 下午4:22:00
 */
@Component
public class RetryScheduledEngine extends TimerTask implements InitializingBean {

    private final static List<RetryInvorkParam> invorkParamList = new ArrayList<>();

    @Autowired
    private org.springframework.core.task.AsyncTaskExecutor asyncTaskExecutor;

    /**
     * 开始任务的
     */
    private synchronized void startEngine() {
        if (CollectionUtil.isNotEmpty(invorkParamList)) {
            Iterator<RetryInvorkParam> iterator = invorkParamList.iterator();
            while (iterator.hasNext()) {
                RetryInvorkParam next = iterator.next();
                if (next.isEnd()) {
                    iterator.remove();
                    break;
                }
                // 执行时间
                Date now = new Date();
                Date nextInvorkTime = next.getNextInvorkTime();

                if (nextInvorkTime != null && nextInvorkTime.before(now)) {
                    // 增加执行次数
                    long currentTimes = next.getCurrentTimes();
                    currentTimes++;
                    next.setCurrentTimes(currentTimes);
                    // 判断是否为最后一次
                    if (currentTimes == next.getRetryTimes()) {
                        next.setEnd(true);
                    }

                    // 计算下一次执行时间;
                    nextInvorkTime = DateUtils.addSeconds(nextInvorkTime, Long.valueOf(next.getInterval()).intValue());
                    next.setNextInvorkTime(nextInvorkTime);

                    // 添加任务
                    asyncTaskExecutor.submit(new RetryRunning(next));
                }
            }
        }
    }

    /**
     * 提交任务
     *
     * @param param
     */
    public void submitTask(RetryInvorkParam param) {
        // 计算执行时间
        Date now = new Date();
        if (param.getDelayed() > 0) {
            param.setNextInvorkTime(DateUtils.addSeconds(now, Long.valueOf(param.getDelayed()).intValue()));
        } else {
            param.setNextInvorkTime(now);
        }
        invorkParamList.add(param);
    }

    @Override
    public void run() {
        this.startEngine();
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        Timer timer = new Timer();
        timer.schedule(this, 5000, 1000);
    }

//  @Retry(delayed=5,interval=10,retryTimes=10)
    public boolean  testRetryInvoke() {
        System.out.println("______________testRetryInvoke被执行_________________");
        return false;
    }
}

最后一步是创建切面配置类:

/**
 * 编写切面
 *
 * @author Anson
 *
 */
@Aspect // 注解声明一个切面
@Component // 受spring管理的容器
public class RetryAspect {

    @Autowired
    private RetryScheduledEngine RetryScheduledEngine;

    @Pointcut("@annotation(com.dafy.common.core.threadpool.retry.Retry)") // 注解声明切点
    public void annotationPointcut() {
    };

    @Around("annotationPointcut()")
    public Object before(ProceedingJoinPoint joinPoint) {

        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        Retry[] retryAnnotations = method.getAnnotationsByType(Retry.class);
        Retry retryAnnotation = retryAnnotations[0];

        long delayed = retryAnnotation.delayed();
        long interval = retryAnnotation.interval();
        int retryTimes = retryAnnotation.retryTimes();

        Object target = joinPoint.getTarget();
        Object[] args = joinPoint.getArgs();

        //构建参数
        RetryInvorkParam param = new RetryInvorkParam();
        param.setArgs(args);
        param.setCurrentTimes(0);
        param.setDelayed(delayed);
        param.setEnd(false);
        param.setInterval(interval);
        param.setInvorkMethod(method);
        param.setRetryTimes(retryTimes);
        param.setTarget(target);

        RetryScheduledEngine.submitTask(param);

        return false;
    }

}

以为这样就大功告成了吗?

--no no no

还有两个很重要的东西;增加两个包;(spring aop默认有接口的类是选用jdk的接口代理的方式来实现代理的;这种方式会导致,不在接口上的类是没有办法实现aop的)我们要增加两个cglib的实现切面的包,然后利用cglib的字节码重写技术实现代理,这样就不会出现调用报错的情况:

<dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
        </dependency>

版本都是1.8.3的

还有一个就是要修改aop的默认实现方式;spring boot需要在启动类中增加一个配置

@EnableAspectJAutoProxy(proxyTargetClass = true)

这样就搞定了;

缺点:1、 这样这些数据都保持在内存中,如果那些执行方法的参数引用的内容发生了变化;就会导致重试结果的变化;(参数最好能够用基本类型)

2、重试的信息是保存在内存中的;重启或者是服务挂了,就会丢失;

3、大量的重试应用数据的保存,可能会导致jvm的内存泄漏;

Tags:

 

转载于: http://www.zhongruitech.com/554422852.html

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值