使用ThreadPoolTaskScheduler实现定时任务调度

在一个外包的项目开发中,甲方未提供xxl-job、quartz等定时任务调度框架,也不能在项目中随意引入新的框架,所以使用多线程ThreadPoolTaskScheduler的方式实现定时任务调度。

本文介绍了在项目限制条件下使用ThreadPoolTaskScheduler实现定时任务调度的解决方案。该方法在不引入额外框架的情况下,利用多线程机制实现了高效的定时任务调度功能,适用于框架受限的开发场景。

1、基本配置类和工具类

/**
 * 线程池配置
 */
@Configuration
public class SchedulingConfig {
    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        // 定时任务执行线程池核心线程数
        int corePoolSize = Runtime.getRuntime().availableProcessors();
        taskScheduler.setPoolSize(corePoolSize);
        taskScheduler.setRemoveOnCancelPolicy(true);
        //线程名称前缀
        taskScheduler.setThreadNamePrefix("TaskSchedulerThreadPool-");
        //线程池在关闭时等待所有任务完成
        taskScheduler.setWaitForTasksToCompleteOnShutdown(true);
        //等待时长,超出时间强制销毁,防止长时间占用导致线程阻塞
        taskScheduler.setAwaitTerminationSeconds(60);
        //拒绝策略
        taskScheduler.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return taskScheduler;
    }
}


/**
 * ScheduledFuture包装类
 */
public final class ScheduledTask {

    public ScheduledFuture<?> future;

    /**
     * 取消定时任务
     */
    public void cancel() {
        ScheduledFuture<?> future = this.future;
        if (future != null) {
            future.cancel(true);
        }
    }
}


/**
 * Runnable实现类
 */
public class SchedulingRunnable implements Runnable {

    private static final Logger logger = LoggerFactory.getLogger(SchedulingRunnable.class);

    private String beanName;

    private String methodName;

    private String params;

    public SchedulingRunnable(String beanName, String methodName) {
        this(beanName, methodName, null);
    }

    public SchedulingRunnable(String beanName, String methodName, String params) {
        this.beanName = beanName;
        this.methodName = methodName;
        this.params = params;
    }

    @Override
    public void run() {
        try {
            Object target = SpringContextUtils.getBean(beanName);

            Method method = null;
            if (!StringUtils.isEmpty(params)) {
                method = target.getClass().getDeclaredMethod(methodName, String.class);
            } else {
                method = target.getClass().getDeclaredMethod(methodName);
            }

            ReflectionUtils.makeAccessible(method);
            if (!StringUtils.isEmpty(params)) {
                method.invoke(target, params);
            } else {
                method.invoke(target);
            }
        } catch (Exception ex) {
            logger.error(String.format("定时任务执行异常 - bean:%s,方法:%s,参数:%s ", beanName, methodName, params), ex);
        }
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        SchedulingRunnable that = (SchedulingRunnable) o;
        if (params == null) {
            return beanName.equals(that.beanName) &&
                    methodName.equals(that.methodName) &&
                    that.params == null;
        }

        return beanName.equals(that.beanName) &&
                methodName.equals(that.methodName) &&
                params.equals(that.params);
    }

    @Override
    public int hashCode() {
        if (params == null) {
            return Objects.hash(beanName, methodName);
        }

        return Objects.hash(beanName, methodName, params);
    }
}  


/**
 * 操作spring的bean工具类
 */
@Component
public class SpringContextUtils implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

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

    public static Object getBean(String name) {
        return applicationContext.getBean(name);
    }

    public static <T> T getBean(Class<T> requiredType) {
        return applicationContext.getBean(requiredType);
    }

    public static <T> T getBean(String name, Class<T> requiredType) {
        return applicationContext.getBean(name, requiredType);
    }

    public static boolean containsBean(String name) {
        return applicationContext.containsBean(name);
    }

    public static boolean isSingleton(String name) {
        return applicationContext.isSingleton(name);
    }

    public static Class<? extends Object> getType(String name) {
        return applicationContext.getType(name);
    }
}

2、定时任务注册

/**
 * 定时任务注册
 */
@Component
public class CronTaskRegistrar implements DisposableBean {

    private final Map<Runnable, ScheduledTask> scheduledTasks = new ConcurrentHashMap<>(16);

    @Resource
    private TaskScheduler taskScheduler;

    public TaskScheduler getScheduler() {
        return this.taskScheduler;
    }

    public void addCronTask(Runnable task, String cronExpression) {
        addCronTask(new CronTask(task, cronExpression));
    }

    public void addCronTask(CronTask cronTask) {
        if (cronTask != null) {
            Runnable task = cronTask.getRunnable();
            if (this.scheduledTasks.containsKey(task)) {
                removeCronTask(task);
            }

            this.scheduledTasks.put(task, scheduleCronTask(cronTask));
        }
    }

    public void removeCronTask(Runnable task) {
        ScheduledTask scheduledTask = this.scheduledTasks.remove(task);
        if (scheduledTask != null)
            scheduledTask.cancel();
    }

    public ScheduledTask scheduleCronTask(CronTask cronTask) {
        ScheduledTask scheduledTask = new ScheduledTask();
        scheduledTask.future = this.taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger());

        return scheduledTask;
    }


    @Override
    public void destroy() {
        for (ScheduledTask task : this.scheduledTasks.values()) {
            task.cancel();
        }

        this.scheduledTasks.clear();
    }
}

3、定时任务预热

/**
 * 定时任务预热
 */
@Service
public class SysJobRunner implements CommandLineRunner {

    private static final Logger logger = LoggerFactory.getLogger(SysJobRunner.class);

    @Resource
    private CronTaskRegistrar cronTaskRegistrar;

    @Override
    public void run(String... args) {
        // 初始加载数据库里状态为正常的定时任务  
        ScheduleSetting existedSysJob = new ScheduleSetting();
        List<ScheduleSetting> jobList = existedSysJob.selectList(new LambdaQueryWrapper<ScheduleSetting>().eq(ScheduleSetting::getStatus, true));
        if (CollectionUtils.isNotEmpty(jobList)) {
            for (ScheduleSetting job : jobList) {
                SchedulingRunnable task = new SchedulingRunnable(job.getBeanName(), job.getMethodName(), job.getMethodParams());
                cronTaskRegistrar.addCronTask(task, job.getCron());
            }
            logger.info("定时任务已加载完毕...启用定时任务:{}个", jobList.size());
        }else {
            logger.info("定时任务启动成功...无定时任务");
        }
    }
} 

4、定时任务配置实体类、mapper、service、serviceImpl

@Data
@TableName("schedule_setting")
@EqualsAndHashCode(callSuper = true)
@ApiModel(value = "定时任务表")
public class ScheduleSetting extends Model<ScheduleSetting> {
    private static final long serialVersionUID = 1L;

    @TableId(type = IdType.ASSIGN_ID)
    @ApiModelProperty(value = "ID")
    private Long id;

    @ApiModelProperty(value = "bean名称")
    private String beanName;

    @ApiModelProperty(value = "方法名称")
    private String methodName;

    @ApiModelProperty(value = "方法参数 json字符串")
    private String methodParams;

    @ApiModelProperty(value = "cron表达式")
    private String cron;

    @ApiModelProperty(value = "状态(1正常 0暂停)")
    private boolean status;

    @ApiModelProperty(value = "备注:存储用于辨识的字段,如排查任务id")
    private String remark;

    @ApiModelProperty(value = "创建时间")
    private Date createTime;

    @ApiModelProperty(value = "更新时间")
    private Date updateTime;

    public static long getSerialVersionUID() {
        return serialVersionUID;
    }

}


@Mapper
public interface  ScheduleSettingMapper extends BaseMapper<ScheduleSetting> {
}


public interface ScheduleSettingService extends IService<ScheduleSetting> {

    boolean addCheckTask(RiskCheckTaskVo riskCheckTaskVo) throws IllegalAccessException;

    boolean updateCheckTask(RiskCheckTaskVo riskCheckTaskVo) throws IllegalAccessException;

    //isChanges 是否启用任务
    boolean add(ScheduleSetting scheduleSetting, boolean isChanges);

    //isChanges 是否重新启用任务
    boolean update(ScheduleSetting scheduleSetting,boolean isChanges);

    boolean del(Long id);

    // 停止/启动任务
    boolean changesStatus(Long id, boolean status);
}


@Service
public class ScheduleSettingServiceImpl extends ServiceImpl<ScheduleSettingMapper, ScheduleSetting> implements ScheduleSettingService {

    private static final Logger logger = LoggerFactory.getLogger(ScheduleSettingServiceImpl.class);

    @Resource
    private CronTaskRegistrar cronTaskRegistrar;

    @Override
    public boolean addCheckTask(RiskCheckTaskVo riskCheckTaskVo) throws IllegalAccessException {
        //转换cron表达式
        String cron = TaskUtils.toCronExpression(riskCheckTaskVo.getCycle(), riskCheckTaskVo.getCycleUnit(),riskCheckTaskVo.getStartTime());
        ScheduleSetting scheduleSetting = new ScheduleSetting();
        scheduleSetting.setBeanName("CheckTask");
        scheduleSetting.setMethodName("taskByParams");
        scheduleSetting.setMethodParams(JSON.toJSONString(riskCheckTaskVo));
        scheduleSetting.setCron(cron);
        scheduleSetting.setStatus(riskCheckTaskVo.getStatus());
        scheduleSetting.setRemark(riskCheckTaskVo.getId().toString());
        return add(scheduleSetting,false);
    }

    @Override
    public boolean updateCheckTask(RiskCheckTaskVo riskCheckTaskVo) throws IllegalAccessException {
        //转换cron表达式
        String cron = TaskUtils.toCronExpression(riskCheckTaskVo.getCycle(), riskCheckTaskVo.getCycleUnit(),riskCheckTaskVo.getStartTime());
        ScheduleSetting scheduleSetting = getOne(new LambdaQueryWrapper<ScheduleSetting>().eq(ScheduleSetting::getRemark, riskCheckTaskVo.getId().toString()));
        scheduleSetting.setMethodParams(JSON.toJSONString(riskCheckTaskVo));
        scheduleSetting.setCron(cron);
        scheduleSetting.setStatus(riskCheckTaskVo.getStatus());
        return update(scheduleSetting,false);
    }

    @Override
    public boolean add(ScheduleSetting scheduleSetting, boolean isChanges) {
        scheduleSetting.setCreateTime(new Date());
        scheduleSetting.setUpdateTime(new Date());
        boolean save = save(scheduleSetting);
        if (!save) {
            logger.error("添加定时任务失败,任务id:{},备注:{}", scheduleSetting.getId(), scheduleSetting.getRemark());
            return false;
        } else {
            if (isChanges && scheduleSetting.getStatus()) {// 添加成功,并且状态是1,直接放入任务器
                SchedulingRunnable task = new SchedulingRunnable(scheduleSetting.getBeanName(), scheduleSetting.getMethodName(), scheduleSetting.getMethodParams());
                cronTaskRegistrar.addCronTask(task, scheduleSetting.getCron());
            }
        }
        return true;
    }

    @Override
    public boolean update(ScheduleSetting scheduleSetting, boolean isChanges) {
        // 查询修改前任务
        ScheduleSetting existedSysJob = getById(scheduleSetting.getId());
        // 修改任务
        scheduleSetting.setCreateTime(new Date());
        scheduleSetting.setUpdateTime(new Date());
        boolean update = updateById(scheduleSetting);
        if (!update) {
            logger.error("修改定时任务失败,任务id:{},备注:{}", scheduleSetting.getId(), scheduleSetting.getRemark());
            return false;
        } else {
            // 修改成功,则先删除任务器中的任务,并重新添加
            SchedulingRunnable task1 = new SchedulingRunnable(existedSysJob.getBeanName(),
                    existedSysJob.getMethodName(),
                    existedSysJob.getMethodParams());
            cronTaskRegistrar.removeCronTask(task1);
            if (isChanges && scheduleSetting.getStatus()) {// 如果修改后的任务状态是1就加入任务器
                SchedulingRunnable task = new SchedulingRunnable(scheduleSetting.getBeanName(),
                        scheduleSetting.getMethodName(),
                        scheduleSetting.getMethodParams());
                cronTaskRegistrar.addCronTask(task, scheduleSetting.getCron());
            }
        }
        return true;
    }

    @Override
    public boolean del(Long id) {
        // 先查询要删除的任务信息
        ScheduleSetting existedSysJob = getById(id);
        // 删除
        boolean del = removeById(id);
        if (!del) {
            logger.error("删除定时任务失败,任务id:{},备注:{}", existedSysJob.getId(), existedSysJob.getRemark());
            return false;
        } else {
            // 删除成功清除定时任务器中的对应任务
            SchedulingRunnable task = new SchedulingRunnable(existedSysJob.getBeanName(),
                    existedSysJob.getMethodName(),
                    existedSysJob.getMethodParams());
            cronTaskRegistrar.removeCronTask(task);
        }
        return del;
    }

    @Override
    public boolean changesStatus(Long id, boolean status) {
        // 修改任务状态
        ScheduleSetting scheduleSetting = getById(id);
        scheduleSetting.setStatus(status);
        boolean update = updateById(scheduleSetting);
        if (!update) {
            logger.error("修改定时任务状态失败,任务id:{},备注:{}", scheduleSetting.getId(), scheduleSetting.getRemark());
            return false;
        }
        // 查询修改后的任务信息
        ScheduleSetting existedSysJob = getById(id);

        // 如果状态是1则添加任务
        if (existedSysJob.getStatus()) {
            SchedulingRunnable task = new SchedulingRunnable(existedSysJob.getBeanName(),
                    existedSysJob.getMethodName(),
                    existedSysJob.getMethodParams());
            cronTaskRegistrar.addCronTask(task, existedSysJob.getCron());
        } else {
            // 否则清除任务
            SchedulingRunnable task = new SchedulingRunnable(existedSysJob.getBeanName(),
                    existedSysJob.getMethodName(),
                    existedSysJob.getMethodParams());
            cronTaskRegistrar.removeCronTask(task);
        }
        return true;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值