AOP方式实现任务超时控制

921 阅读1分钟
  • pom引入切面依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
  • 注解类
package com.per.aop;

import java.lang.annotation.*;

/**
 * 超时控制注解
 *
 * @author hhj
 * @date 2021/12/30 15:20
 */
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Timeout {
    /**
     * 超时时间,返回毫秒
     */
    long time() default 3000L;

    /**
     * 返回类型,需要根据类型来构造超时返回的数据
     */
    Class clazz() default Object.class;

}
  • 切面类
package com.per.aop;

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import lombok.extern.slf4j.Slf4j;
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.stereotype.Component;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;

/**
 * 超时控制切面
 *
 * @author hhj
 * @date 2021/12/30 15:17
 */
@Aspect
@Slf4j
@Component
public class TimeoutAspect {

    /**
     * 超时控制线程池,该池中线程的平均耗时较长,为避免其他线程等待时间过长,使用同步队列,coreSize和maxSize根据机器配置按需调整
     */
    public static final ThreadPoolExecutor TIMEOUT_POOL = new ThreadPoolExecutor(2, 128,
            60L, TimeUnit.SECONDS, new SynchronousQueue<>(),
            new ThreadFactoryBuilder().setNameFormat("TimeoutAspect-pool-%d").build());

    @Pointcut(value = "@annotation(com.per.aop.Timeout)")
    public void pointCut() {
    }

    @Around(value = "pointCut()")
    public Object timeOut(ProceedingJoinPoint joinPoint) throws InterruptedException {
        log.info("timeout aop enter...");
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        Timeout timeOut = method.getDeclaredAnnotation(Timeout.class);
        //返回值类型,如果没有设置,则使用方法的returnType
        Class returnClazz = Object.class.equals(timeOut.clazz()) ? method.getReturnType() : timeOut.clazz();
        Future future = TIMEOUT_POOL.submit(() -> {
            log.info("timeout aop method start...");
            try {
                return joinPoint.proceed();
            } catch (Throwable throwable) {
                //根据传入Class生成默认值
                return getDefaultValue(returnClazz);
            }
        });
        try {
            return future.get(timeOut.time(), TimeUnit.MILLISECONDS);
        } catch (InterruptedException | ExecutionException | TimeoutException e) {
            future.cancel(true);
            log.error("timeout aop method timeout:{}", timeOut.time());
            //根据传入Class生成默认值
            return getDefaultValue(returnClazz);
        }
    }

    /**
     * 超过时间后,需要返回的默认值,根据注解传入的Class生成
     *
     * @param clazz 返回值的类型
     * @return 默认值
     */
    private static Object getDefaultValue(Class clazz) {
        if (clazz == null || Object.class.equals(clazz)) {
            return null;
        } else if (String.class.equals(clazz)) {
            return "";
        } else if (ArrayList.class.equals(clazz) || List.class.equals(clazz)) {
            return new ArrayList<>(0);
        } else if (HashMap.class.equals(clazz) || Map.class.equals(clazz)) {
            return new HashMap<>(0);
        }
        //使用反射调用无参构造函数返回默认值,如果想自定义返回值,在上面添加即可
        try {
            Constructor constructor = clazz.getConstructor();
            return constructor.newInstance();
        } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
            //该类没有无参构造函数时,返回空
            log.error("There is no non-parameter constructor, return null, class:{}", clazz);
            return null;
        } catch (Exception e) {
            log.error("return default value error", e);
            return null;
        }
    }

}
  • 在想要控制超时时间的方法上加入注解即可
@Timeout(time = 5000L)
public String getToken() {
    //do something
}