ThreadPoolTaskScheduler 定时任务应用

241 阅读5分钟

org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler定时任务调度线程池

CREATE TABLE `t_sys_job` (
  `id` bigint(20) unsigned zerofill NOT NULL AUTO_INCREMENT COMMENT '任务key',
  `job_name` varchar(64) NOT NULL COMMENT '任务名称',
  `bean_class` varchar(128) NOT NULL COMMENT '类路径',
  `cron` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'cron表达式',
  `status` tinyint(1) NOT NULL,
  `is_deleted` tinyint(1) DEFAULT '0' COMMENT '删除标识 1是 0否',
  `create_time` datetime DEFAULT NULL,
  `update_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

@Configuration
@Slf4j
@EnableScheduling
public class SchedulingConfigure {

    @Bean
    public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
        ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
        threadPoolTaskScheduler.setPoolSize(20);
        threadPoolTaskScheduler.setThreadNamePrefix("schedule-task-");
        threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown(true);
        threadPoolTaskScheduler.setAwaitTerminationSeconds(60);
        log.info(">>>ThreadPoolTaskScheduler定时任务线程池初始化配置完成");
        return threadPoolTaskScheduler;
    }

}
@Data
@TableName("t_sys_job")
public class SysJob  {

    private static final long serialVersionUID = 1L;

    /**
     * 任务名称
     */
    private String jobName;

    /**
     * 类路径
     */
    private String beanClass;

    /**
     * cron表达式
     */
    private String cron;

    /**
     * 状态值
     */
    private Integer status;

	 /**
     * 主键id
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    /**
     * 创建时间
     */
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    /**
     * 更新时间
     */
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime updateTime;

    /**
     * 删除标志
     * 1 删除
     * 0 未删除
     */
    private Integer isDeleted;

}
public interface ISysJobService extends IService<SysJob> {

    /**
     * 获取全部任务列表
     *
     * @return List<SysJob>
     */
    List<SysJob> listAllJob();

    /**
     * 更新任务状态
     *
     * @param jobId  工作id
     * @param status 状态
     */
    void updateJobStatus(Long jobId, Integer status);

    /**
     * 新增或更新定时任务信息
     *
     * @param entity 定时任务实体类
     */
    void addOrUpdate(SysJob entity);

    /**
     * 删除定时任务
     *
     * @param jobId 定时任务id
     */
    void deleteByJobId(Long jobId);

    /**
     * 启动任务
     *
     * @param jobId 工作id
     */
    void startJob(Long jobId);

    /**
     * 停止任务
     *
     * @param jobId 工作id
     */
    void stopJob(Long jobId);
}
@Service
public class SysJobServiceImpl extends ServiceImpl<SysJobMapper, SysJob> implements ISysJobService {

    @Resource
    private ScheduledJobService scheduledJobService;

    @Override
    public List<SysJob> listAllJob() {
        LambdaQueryWrapper<SysJob> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(SysJob::getIsDeleted, 0);
        return list(wrapper);
    }

    @Override
    public void updateJobStatus(Long jobId, Integer status) {
        LambdaUpdateWrapper<SysJob> updateWrapper = Wrappers.lambdaUpdate();
        updateWrapper.set(SysJob::getStatus, status)
                .set(SysJob::getUpdateTime, LocalDateTime.now())
                .eq(SysJob::getId, jobId);
        update(updateWrapper);
    }

    @Override
    public void addOrUpdate(SysJob entity) {
        // 检查任务是否重复创建
        LambdaQueryWrapper<SysJob> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(SysJob::getJobName, entity.getJobName())
                .eq(SysJob::getBeanClass, entity.getBeanClass())
                .eq(SysJob::getIsDeleted, 0);
        if (entity.getId() != null) {
            wrapper.ne(SysJob::getId, entity.getId());
        }
        if (list(wrapper).size() > 0) {
            throw new BaseException("任务重复创建");
        }
        // 新增任务,如果是编辑,则删除原来任务创建新任务
        if (entity.getId() != null) {
            // 删除原任务
            SysJob sysJob = getById(entity.getId());
            if (JobStatusEnum.RUNNING.getStatus().equals(sysJob.getStatus())) {
                throw new BaseException("任务运行中,不允许删除");
            }
            sysJob.setJobName(sysJob.getJobName() + "_已删除");
            sysJob.setStatus(JobStatusEnum.DELETED.getStatus());
            // 修改原任务状态,以及从调度器中删除
            updateById(sysJob);
            scheduledJobService.cancel(sysJob.getId());
            // 置空id进行新增覆盖
            entity.setId(null);
        }
        // 创建任务但未加入调度器
        entity.setStatus(JobStatusEnum.NOT_SCHEDULE.getStatus());
        // 新增任务
        save(entity);
    }

    @Override
    public void deleteByJobId(Long jobId) {
        SysJob sysJob = getById(jobId);
        if (JobStatusEnum.RUNNING.getStatus().equals(sysJob.getStatus())) {
            throw new BaseException("任务运行中,不允许删除");
        }
        // 删除任务
        LambdaUpdateWrapper<SysJob> updateWrapper = Wrappers.lambdaUpdate();
        updateWrapper.set(SysJob::getIsDeleted, 1)
                .set(SysJob::getUpdateTime, LocalDateTime.now())
                .eq(SysJob::getId, jobId);
        update(updateWrapper);

        // 从调度器中移除任务
        scheduledJobService.cancel(jobId);
    }

    @Override
    public void startJob(Long jobId) {
        scheduledJobService.start(jobId);
    }

    @Override
    public void stopJob(Long jobId) {
        scheduledJobService.stop(jobId);
    }

}
@RestController
@RequestMapping("/sys/job")
public class SysJobController {

    @Resource
    private ISysJobService sysJobService;

    @GetMapping("/all")
    public ResultInfo getAllJobList() {
        return ResultInfo.success(sysJobService.listAllJob());
    }

    @PostMapping("/addOrUpdate")
    public ResultInfo addJob(@RequestBody SysJob sysJob) {
        sysJobService.addOrUpdate(sysJob);
        return ResultInfo.success();
    }

    @PostMapping("/delete/{jobId}")
    public ResultInfo deleteByJobId(@PathVariable("jobId") Long jobId) {
        sysJobService.deleteByJobId(jobId);
        return ResultInfo.success("删除成功");
    }

    @PostMapping("/start/{jobId}")
    public ResultInfo start(@PathVariable("jobId") Long jobId) {
        sysJobService.startJob(jobId);
        return ResultInfo.success("启动成功");
    }

    @PostMapping("/stop/{jobId}")
    public ResultInfo stop(@PathVariable("jobId") Long jobId) {
        sysJobService.stopJob(jobId);
        return ResultInfo.success("暂停成功");
    }
}

public enum JobStatusEnum {

    /**
     * 未加入调度器,(创建还未启动)
     */
    NOT_SCHEDULE(0, "未加入调度器"),

    /**
     * 加入调度器,但未运行,(已启动但是还没运行)
     */
    SCHEDULED_BUT_NOT_RUNNING(1, "加入调度器,但未运行"),

    /**
     * 运行中
     */
    RUNNING(2, "运行中"),

    /**
     * 从调度器中已删除
     */
    DELETED(3, "任务已删除"),
    ;

    private Integer status;

    private String detail;

    JobStatusEnum(Integer status, String detail) {
        this.status = status;
        this.detail = detail;
    }

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }

    public String getDetail() {
        return detail;
    }

    public void setDetail(String detail) {
        this.detail = detail;
    }
}
@Component
public class SpringBeanUtils implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringBeanUtils.applicationContext = applicationContext;
    }

    /**
     * 获取applicationContext
     */
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    /**
     * 通过name获取 Bean.
     */
    public  Object getBean(String name) {
        return getApplicationContext().getBean(name);
    }

    /**
     * 通过class获取Bean.
     */
    public  <T> T getBean(Class<T> clazz) {
        return getApplicationContext().getBean(clazz);
    }

    /**
     * 通过name,以及Clazz返回指定的Bean
     */
    public  <T> T getBean(String name, Class<T> clazz) {
        return getApplicationContext().getBean(name, clazz);
    }
}
@Service
@Slf4j
public class ScheduledJobService {

    @Resource
    private SpringBeanUtils springBeanUtils;
    @Resource
    private ISysJobService sysJobService;
    @Resource
    private ThreadPoolTaskScheduler threadPoolTaskScheduler;

    /**
     * 保存已经加入调度器的任务map
     */
    private final ConcurrentHashMap<Long, ScheduledFuture<?>> scheduledFutureMap = new ConcurrentHashMap<>();

    private final ReentrantLock lock = new ReentrantLock();

    /**
     * 初始化启动任务
     */
    public void init() {
        List<SysJob> sysJobs = sysJobService.listAllJob();
        if (sysJobs.size() == 0) {
            return;
        }
        for (SysJob sysJob : sysJobs) {
            if (JobStatusEnum.NOT_SCHEDULE.getStatus().equals(sysJob.getStatus())
                    || this.isScheduled(sysJob.getId())) {
                // 任务未加入调度器或已经加入调度器的过滤
                continue;
            }
            // 状态为已加入调度器或上次运行中还未结束的 都加入调度器中等待下次运行
            this.doScheduleJob(sysJob);
        }
        log.info("定时任务初始化完成");
    }

    /**
     * 启动任务
     *
     * @param jobId job主键id
     */
    public void start(Long jobId) {
        log.info("启动任务:-> jobId_{}", jobId);
        // 加入调度器
        schedule(jobId);
        log.info("启动任务结束:-> jobId_{}", jobId);
        // 更新任务状态
        sysJobService.updateJobStatus(jobId, JobStatusEnum.SCHEDULED_BUT_NOT_RUNNING.getStatus());
    }

    /**
     * 停止任务
     *
     * @param jobId job主键id
     */
    public void stop(Long jobId) {
        log.info("停止任务:-> jobId_{}", jobId);
        // 取消任务
        cancel(jobId);
        log.info("停止任务结束:-> jobId_{}", jobId);
        // 更新表中任务状态为已停止
        sysJobService.updateJobStatus(jobId, JobStatusEnum.NOT_SCHEDULE.getStatus());
    }

    /**
     * 取消任务
     *
     * @param jobId job主键id
     */
    public void cancel(Long jobId) {
        // 任务是否存在
        if (scheduledFutureMap.containsKey(jobId)) {
            ScheduledFuture<?> scheduledFuture = scheduledFutureMap.get(jobId);
            if (!scheduledFuture.isCancelled()) {
                // 取消调度
                scheduledFuture.cancel(true);
            }
        }
    }

    private void schedule(Long jobId) {
        // 添加锁,只允许单个线程访问,防止任务启动多次
        lock.lock();
        try {
            if (isScheduled(jobId)) {
                log.error("任务jobId_{}已经加入调度器,无需重复操作", jobId);
                return;
            }
            // 通过jobKey查询jobBean对象
            SysJob sysJob = Optional.ofNullable(sysJobService.getById(jobId))
                    .orElseThrow(() -> new BaseException("任务不存在"));
            // 启动定时任务
            doScheduleJob(sysJob);
        } finally {
            // 释放锁资源
            lock.unlock();
        }
    }

    /**
     * 任务是否已经进入调度器
     *
     * @param jobId 任务id
     * @return {@link Boolean}
     */
    private Boolean isScheduled(Long jobId) {
        if (scheduledFutureMap.containsKey(jobId)) {
            return !scheduledFutureMap.get(jobId).isCancelled();
        }
        return false;
    }

    /**
     * 执行启动任务
     *
     * @param sysJob 任务实体类对象
     */
    private void doScheduleJob(SysJob sysJob) {
        Long jobId = sysJob.getId();
        String beanClass = sysJob.getBeanClass();
        String jobName = sysJob.getJobName();
        String cron = sysJob.getCron();
        // 从Spring中获取目标的job业务实现类
        BaseJob baseJob = parseFrom(beanClass);
        if (baseJob == null) {
            return;
        }
        baseJob.setJobId(jobId);
        baseJob.setJobName(jobName);

        ScheduledFuture<?> scheduledFuture = threadPoolTaskScheduler.schedule(baseJob,
                triggerContext -> {
                    CronTrigger cronTrigger = new CronTrigger(cron);
                    return cronTrigger.nextExecutionTime(triggerContext);
                });

        log.info("任务加入调度器 -> jobId:{},jobName:{}", jobId, jobName);

        // 将启动的任务放入map
        assert scheduledFuture != null;
        scheduledFutureMap.put(jobId, scheduledFuture);
    }

    private BaseJob parseFrom(String beanClass) {
        try {
            Class<?> clazz = Class.forName(beanClass);
            return (BaseJob) springBeanUtils.getBean(clazz);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

}
@Data
public abstract class BaseJob implements Runnable {

    /**
     * 任务id
     */
    private Long jobId;

    /**
     * 任务名称
     */
    private String jobName;

}

@Component
@Slf4j
public class DemoJob extends BaseJob {

    @Resource
    private ISysJobService sysJobService;

    @Override
    public void run() {
        Long jobId = getJobId();
        // 修改状态为运行中
        sysJobService.updateJobStatus(jobId, JobStatusEnum.RUNNING.getStatus());
        try {
            //TODO 业务逻辑代码执行
            log.info("DemoJob.class_jobId_{},jobName_{}_开始执行..当前时间{}", jobId, getJobName(), LocalDateTime.now());
            Thread.sleep(3000L);
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            sysJobService.updateJobStatus(jobId, JobStatusEnum.SCHEDULED_BUT_NOT_RUNNING.getStatus());
        }

    }
}
@SpringBootApplication(scanBasePackages = {"com.example.grape"})
@MapperScan("com.example.grape.dao.mapper")
@EnableScheduling
public class GrapeApplication {

    public static void main(String[] args) {
        SpringApplication.run(GrapeApplication.class, args);
    }

}