Spring 异步任务简明教程

第一步,为异步任务配置所需的线程池 和 异常处理器

线程池就不多解释了,这里用的是Spring提供的线程池;也可以用java库提供的;

异常处理器会捕获异步任务抛出的异常进行处理;当然前提是你得抛出;

package com.imooc.ecommerce.config;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.lang.reflect.Method;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

@Slf4j
@EnableAsync//开启Spring异步任务支持,放到启动类上也可以
@Configuration
public class AsyncPoolConfig implements AsyncConfigurer {

    //把我们自定义的线程池注入到IOC容器中,这个线程池会用来执行我们的异步任务
    @Override
    @Bean
    public Executor getAsyncExecutor() {

        //这个是Spring提供的线程池,当然我们也可以用Java库提供的
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心线程数
        executor.setCorePoolSize(10);
        //最大线程数
        executor.setMaxPoolSize(20);
        //阻塞队列大小
        executor.setQueueCapacity(20);
        //线程存活时间(应该是超过核心线程数的线程的存活时间)
        executor.setKeepAliveSeconds(60);
        //自定义线程名字前缀
        executor.setThreadNamePrefix("QiuQiu");

        //关闭线程池时会等待所有任务执行完毕后在关闭
        executor.setWaitForTasksToCompleteOnShutdown(true);
        //在关闭线程池时,如果一个任务的执行时长超过了60秒仍未执行完(有可能是阻塞),会强制关闭
        executor.setAwaitTerminationSeconds(60);
        //定义拒绝策略
        //CallerRunsPolicy 如果阻塞队列满了,直接在提交该任务的线程中运行该任务
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //初始化线程池,初始化core线程
        executor.initialize();

        return executor;
    }

    //指定系统中的异步任务在出现异常时用到的处理器
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new AsyncExceptionHandler();
    }

    //自定义异步任务异常处理器
    @SuppressWarnings("all")
    class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler {

        //三个参数依次是异常,抛出异常的方法和方法的参数
        @Override
        public void handleUncaughtException(Throwable throwable, Method method, Object... objects) {
            throwable.printStackTrace();
            log.error("Async Error:[{}], Method:[{}], Param:[{}]", throwable.getMessage(), method.getName(), JSON.toJSONString(objects));
        }
    }
}

 

第二步,编写需要异步执行的业务代码

很简单,interface的代码就省略了

package com.imooc.ecommerce.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class AsyncTestServiceImpl implements IAsyncTestService{
    @Override
    @Async("getAsyncExecutor")//此方法异步执行,括号内参数表明使用的线程池
    public void asyncTest(String taskId) {
        System.out.println("开始执行异步任务" + taskId);
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println("正在执行业务代码.......");
        //模拟异常
        /*if (1 == 1) {
            throw new RuntimeException("模拟异常!!!");
        }*/
        
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("异步任务执行结束" + taskId);
    }
}

第三步,实现一个异步任务的管理器,并使用AOP增强业务代码,主要是为了监控异步任务的执行状态

aop代码如下

package com.imooc.ecommerce.monitor;

import com.imooc.ecommerce.constant.AsyncTaskStatusEnum;
import com.imooc.ecommerce.service.AsyncTaskManager;
import com.imooc.ecommerce.vo.AsyncTaskInfo;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Date;

@Slf4j
@Aspect
@Component
public class AsyncTaskMonitor {

    @Resource
    private AsyncTaskManager asyncTaskManager;

    @Around("execution(* com.imooc.ecommerce.service.AsyncTestServiceImpl.*(..))")
    public Object taskHandle(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {

        //获取taskId
        String taskId = proceedingJoinPoint.getArgs()[0].toString();

        //获取任务信息,在提交任务时就已经放入到容器中了
        AsyncTaskInfo taskInfo = asyncTaskManager.getTaskInfo(taskId);
        log.info("AsyncTaskMonitor is monitoring async task:[{}]", taskId);

        //设置为运行状态,并重新放入容器
        taskInfo.setStatus(AsyncTaskStatusEnum.RUNNING);
        asyncTaskManager.setTaskInfo(taskInfo);

        AsyncTaskStatusEnum status;
        Object result;

        try {
            result = proceedingJoinPoint.proceed();
            status = AsyncTaskStatusEnum.SUCCESS;
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            log.error("异常出现了!!!");
            result = null;
            status =AsyncTaskStatusEnum.FAILED;
            throw throwable;
        }

        //设置异步任务其他的信息,再次重新放入到容器中
        taskInfo.setEndTime(new Date());
        taskInfo.setStatus(status);
        taskInfo.setTotalTime(String.valueOf(taskInfo.getEndTime().getTime() - taskInfo.getStartTime().getTime()));
        asyncTaskManager.setTaskInfo(taskInfo);

        return result;
    }

}

管理器代码如下

package com.imooc.ecommerce.service;

import com.imooc.ecommerce.constant.AsyncTaskStatusEnum;
import com.imooc.ecommerce.vo.AsyncTaskInfo;
import lombok.extern.slf4j.Slf4j;

import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * 异步任务执行管理器
 * 对异步任务进行包装管理,记录并塞入异步任务执行信息
 */
@Slf4j
@Component
public class AsyncTaskManager {

    /**
     * 异步任务执行信息容器
     */
    private final Map<String, AsyncTaskInfo> taskContainer = new HashMap<>(16);

    @Resource
    private IAsyncTestService asyncTestService;

    //初始化异步任务
    public AsyncTaskInfo initTask() {
        AsyncTaskInfo taskInfo = new AsyncTaskInfo();
        //设置一个唯一的异步任务id, 只要唯一即可
        taskInfo.setTaskId(UUID.randomUUID().toString());
        taskInfo.setStatus(AsyncTaskStatusEnum.STARTED);
        taskInfo.setStartTime(new Date());

        taskContainer.put(taskInfo.getTaskId(), taskInfo);
        return taskInfo;
    }

    //提交异步任务
    public AsyncTaskInfo submit() {
        AsyncTaskInfo taskInfo = initTask();
        asyncTestService.asyncTest(taskInfo.getTaskId());
        return taskInfo;
    }

    //设置异步任务执行状态信息
    public void setTaskInfo(AsyncTaskInfo asyncTaskInfo) {
        taskContainer.put(asyncTaskInfo.getTaskId(), asyncTaskInfo);
    }

    //获取异步任务执行状态信息
    public AsyncTaskInfo getTaskInfo(String taskId) {
        return taskContainer.get(taskId);
    }
}

 

最后,在controller层调用管理器中的submit方法即可执行异步任务。调用getTaskInfo方法可随时查看异步任务的执行状态。

@GetMapping("/test")
    public AsyncTaskInfo test() {
        return asyncTaskManager.submit();
    }

@GetMapping("/task-info")
    public AsyncTaskInfo getTaskInfo(@RequestParam String taskId) {
        return asyncTaskManager.getTaskInfo(taskId);
    }

其他代码如下:

package com.imooc.ecommerce.vo;

import com.imooc.ecommerce.constant.AsyncTaskStatusEnum;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class AsyncTaskInfo {

    private String taskId;

    //执行状态
    private AsyncTaskStatusEnum status;

    private Date startTime;

    private Date endTime;

    private String totalTime;
}
package com.imooc.ecommerce.constant;

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * 异步任务状态枚举
 */
@Getter
@AllArgsConstructor
public enum AsyncTaskStatusEnum {

    STARTED(0, "已经启动"),
    RUNNING(1, "正在运行"),
    SUCCESS(2, "执行成功"),
    FAILED(3, "执行失败");

    //执行状态码
    private final int state;

    //执行状态描述
    private final String stateInfo;

}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值