龙空技术网

SpringBoot自定义cron表达式注册定时任务

肚脐眼女孩 752

前言:

此时小伙伴们对“java自定义定时任务”大体比较讲究,小伙伴们都需要学习一些“java自定义定时任务”的相关资讯。那么小编在网上网罗了一些对于“java自定义定时任务””的相关文章,希望朋友们能喜欢,你们快快来了解一下吧!

一、原理1、使用Spring自带的TaskScheduler注册任务2、注册后返回:ScheduledFuture,用于取消定时任务3、注册任务后不会马上取消任务,所以将任务缓存。在需要取消任务的时候调用取消接口取消4、cron表达式可以由前端或者后端生成。实现中会校验cron表达式

public class TestScheduled {    /**     * 1、使用Spring自带的TaskScheduler注册任务     * 2、注册后返回:ScheduledFuture,用于取消定时任务     */    @Resource    private TaskScheduler taskScheduler;    public void registrarTask() {        //具体的任务Runnable(一般使用类实现Runnable接口)        Runnable taskRunnable = new Runnable() {            @Override            public void run() {            }        };        //cron表达式触发器        CronTrigger trigger = new CronTrigger("0/5 * * * * ?");        //开启定时任务的真正方法        ScheduledFuture<?> future = this.taskScheduler.schedule(taskRunnable, trigger);        //取消定时任务        future.cancel(true);    }}
二、具体实现1、配置任务调度器作用:设置:核心线程数:可同时执行任务数;设置线程名称前缀可以不配置。不配置就默认使用spring自带的
package com.cc.ssd.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.scheduling.TaskScheduler;import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;/** TaskScheduler任务调度器配置类 * @since 2023/4/21 0021 * @author CC **/@Configurationpublic class CronTaskConfig {    /**     * 任务调度器自定义配置     */    @Bean(name = "taskScheduler")    public TaskScheduler taskScheduler() {        // 任务调度线程池        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();        // 定时任务执行线程池核心线程数:可同时执行4个任务        taskScheduler.setPoolSize(4);        taskScheduler.setRemoveOnCancelPolicy(true);        // 线程名称前缀        taskScheduler.setThreadNamePrefix("Cs-ThreadPool-");        return taskScheduler;    }}
2、定时任务注册类作用:缓存、注册定时任务;还可以查询、删除定时任务
package com.cc.ssd.registrar;import com.cc.ssd.task.CronTaskFuture;import com.cc.ssd.task.CronTaskRunnable;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.BeanUtils;import org.springframework.beans.factory.DisposableBean;import org.springframework.scheduling.TaskScheduler;import org.springframework.scheduling.config.CronTask;import org.springframework.scheduling.support.CronExpression;import org.springframework.stereotype.Component;import org.springframework.util.Assert;import javax.annotation.Resource;import java.time.LocalDateTime;import java.time.format.DateTimeFormatter;import java.util.*;import java.util.concurrent.ConcurrentHashMap;import java.util.stream.Collectors;/** 注册定时任务:缓存定时任务、注册定时任务到调度中心 * @author CC **/@Componentpublic class CronTaskRegistrar implements DisposableBean {    private static final Logger log = LoggerFactory.getLogger(CronTaskRegistrar.class);    /**     * 缓存任务     * key:具体的任务     * value:注册定时任务后返回的ScheduledFuture     */    private final Map<Runnable, CronTaskFuture> scheduledTasks = new ConcurrentHashMap<>(16);    /**     * 使用自定义的任务调度配置     */    @Resource(name = "taskScheduler")    private TaskScheduler taskScheduler;    /** 获取任务调度配置     * @return 任务调度配置     */    public TaskScheduler getTaskScheduler() {        return this.taskScheduler;    }    /** 新增定时任务1     *  存在任务:删除此任务,重新新增这个任务     * @param taskRunnable 执行的具体任务定义:taskRunnable 实现Runnable     * @param cronExpression cron表达式     */    public void addCronTask(Runnable taskRunnable, String cronExpression) {        //验证cron表达式是否正确        boolean validExpression = CronExpression.isValidExpression(cronExpression);        if (!validExpression) {            throw new RuntimeException("cron表达式验证失败!");        }        //获取下次执行时间        CronExpression parse = CronExpression.parse(cronExpression);        LocalDateTime next = parse.next(LocalDateTime.now());        if (Objects.nonNull(next)) {            //定时任务下次执行的时间            String format = next.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));            log.info("定时任务下次执行的时间:{}", format);        }        //封装成 CronTask(cron任务)        CronTask cronTask = new CronTask(taskRunnable, cronExpression);        this.addCronTask(cronTask);    }    /** 新增定时任务2     * @param cronTask :<p>CronTask用于在指定时间间隔内执行定时任务。</p>     *                   <p>它是通过CronTrigger来实现的,CronTrigger是一个基于cron表达式的触发器,</p>     *                   <p>可以在指定的时间间隔内触发任务执行。</p>     * @since 2023/4/21 0021     * @author CC     **/    private void addCronTask(CronTask cronTask) {        if (Objects.nonNull(cronTask)) {            //1有这个任务,先删除这个任务。再新增            Runnable task = cronTask.getRunnable();            String taskId = null;            if (task instanceof CronTaskRunnable) {                taskId = ((CronTaskRunnable) task).getTaskId();            }            //通过任务id获取缓存的任务,如果包含则删除,然后新增任务            Runnable taskCache = this.getTaskByTaskId(taskId);            if (Objects.nonNull(taskCache) && this.scheduledTasks.containsKey(taskCache)) {                this.removeCronTaskByTaskId(taskId);            }            //2注册定时任务到调度中心            CronTaskFuture scheduledFutureTask = this.scheduleCronTask(cronTask);            //3缓存定时任务            this.scheduledTasks.put(task, scheduledFutureTask);            //todo cc 4可以将任务保存到数据库中……重新启动程序然后加载数据库中的任务到缓存中……        }    }    /** 注册 ScheduledTask 定时任务     * @param cronTask cronTask     * @return 注册定时任务后返回的 ScheduledFutureTask     */    private CronTaskFuture scheduleCronTask(CronTask cronTask) {        //注册定时任务后记录的Future        CronTaskFuture scheduledTask = new CronTaskFuture();        //开启定时任务的真正方法        scheduledTask.future = this.taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger());//        scheduledTask.setThreadLocal(this.taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger()));        return scheduledTask;    }    /** 获取任务列表     * @return     */    public List<CronTaskRunnable> getScheduledTasks() {        List<CronTaskRunnable> tasks = new ArrayList<>();        Set<Runnable> keySet = scheduledTasks.keySet();        keySet.forEach(key -> {            CronTaskRunnable task = new CronTaskRunnable();            if (key instanceof CronTaskRunnable) {                CronTaskRunnable taskParent = (CronTaskRunnable) key;                BeanUtils.copyProperties(taskParent, task);            }            tasks.add(task);        });        return tasks.stream()                .sorted(Comparator.comparing(CronTaskRunnable::getTaskId))                .collect(Collectors.toList());    }    /** 根据任务id删除单个定时任务     * @param taskId 任务id     */    public void removeCronTaskByTaskId(String taskId) {        //通过任务id获取任务        Runnable task = this.getTaskByTaskId(taskId);        //需要通过任务id获取任务,然后再移除        CronTaskFuture cronTaskFuture = this.scheduledTasks.remove(task);        if (Objects.nonNull(cronTaskFuture)) {            cronTaskFuture.cancel();        }    }    /** 通过任务id获取任务。未查询到返回null     * @param taskId 任务id     * @return java.lang.Runnable     * @since 2023/4/21 0021     * @author CC     **/    private Runnable getTaskByTaskId(String taskId) {        Assert.notNull(taskId, "任务id不能为空!");        Set<Map.Entry<Runnable, CronTaskFuture>> entries = scheduledTasks.entrySet();        //根据任务id获取该任务缓存        Map.Entry<Runnable, CronTaskFuture> rcf = entries.stream().filter(rf -> {            Runnable key = rf.getKey();            String taskId1 = null;            if (key instanceof CronTaskRunnable) {                taskId1 = ((CronTaskRunnable) key).getTaskId();            }            return taskId.equals(taskId1);        }).findAny().orElse(null);        if (Objects.nonNull(rcf)) {            return rcf.getKey();        }        return null;    }    /** 删除所有的定时任务     *     DisposableBean是Spring框架中的一个接口,它定义了一个destroy()方法,     *     用于在Bean销毁时执行清理工作。     *     当一个Bean实现了DisposableBean接口时,     *     Spring容器会在该Bean销毁时自动调用destroy()方法,     *     以便进行一些清理工作,例如释放资源等。     *     如果您的Bean需要在销毁时执行一些清理工作,     *     那么实现DisposableBean接口是一个很好的选择。     */    @Override    public void destroy() {        //关闭所有定时任务        for (CronTaskFuture task : this.scheduledTasks.values()) {            task.cancel();        }        //清空缓存        this.scheduledTasks.clear();        log.info("取消所有定时任务!");        //todo cc 修改或删除数据库的任务    }}
3、定时任务的执行结果ScheduledFuture作用:CronTaskFuture类中使用的是ScheduledFuture对象来表示定时任务的执行结果。
package com.cc.ssd.task;import java.util.Objects;import java.util.concurrent.ScheduledFuture;/** CronTaskFuture类中使用的是ScheduledFuture对象来表示定时任务的执行结果。 *  ——最后ps:也可以不要这个记录类,直接缓存ScheduledFuture对象。 *  用来记录单独的Future、定时任务注册任务后产生的 * @author CC **/public final class CronTaskFuture {    /** 每个线程一个副本     * 经过测试这里不能使用ThreadLocal     *///    private static final ThreadLocal<ScheduledFuture<?>> THREAD_LOCAL = new ThreadLocal<>();    /** 最后ps:由于ScheduledFuture是线程安全的。这里不用 volatile 或者 ThreadLocal     *      注册任务后返回的:ScheduledFuture 用于记录并取消任务     *      这两个都可以不使用。直接给future赋值     *          volatile:线程之间可见:volatile用于实现多线程之间的可见性和一致性,保证数据的正确性。     *          ThreadLocal:用于实现线程封闭,保证线程安全     * 使用建议:     *      CronTaskFuture类中使用的是ScheduledFuture对象来表示定时任务的执行结果。     *      ScheduledFuture对象是线程安全的,因此不需要使用volatile关键字来保证多线程同步。     *      如果需要在多线程中使用线程本地变量,可以使用ThreadLocal。     *      因此,建议在CronTaskFuture类中使用ScheduledFuture对象,而不是使用volatile或ThreadLocal。     *      另外,如果需要在Spring容器销毁时执行一些清理操作,可以实现DisposableBean接口,并在destroy()方法中进行清理操作。     */    public ScheduledFuture<?> future;//    public volatile ScheduledFuture<?> future;//    public void setThreadLocal(ScheduledFuture<?> future){//        THREAD_LOCAL.set(future);//    }    /**     * 取消当前定时任务     */    public void cancel() {        try {//            ScheduledFuture<?> future = THREAD_LOCAL.get();            ScheduledFuture<?> future = this.future;            if (Objects.nonNull(future)) {                future.cancel(true);            }        } catch (Exception e) {            throw new RuntimeException("销毁定时任务失败!");        } finally {//            THREAD_LOCAL.remove();        }    }}
4、具体的任务。实现Runable接口任务处理的方式按照自己的需求去实现即可
package com.cc.ssd.task;import lombok.Data;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;/** 具体任务实现 * @author CC * @since 2023/4/21 0021 */@Datapublic class CronTaskRunnable implements Runnable {    private static final Logger log = LoggerFactory.getLogger(CronTaskRunnable.class);    /**     * 任务id(必须唯一)     */    private String taskId;    /**     * 任务类型:自定义     */    private Integer taskType;    /**     * 任务名字     */    private String taskName;    /**     * 任务参数     */    private Object[] params;    public CronTaskRunnable() {    }    public CronTaskRunnable(String taskId, Integer taskType, String taskName, Object... params) {        this.taskId = taskId;        this.taskType = taskType;        this.taskName = taskName;        this.params = params;    }    /** 执行任务     * @since 2023/4/21 0021     * @author CC     **/    @Override    public void run() {        long start = System.currentTimeMillis();        //具体的任务。        log.info("\n\t {}号.定时任务开始执行 - taskId:{},taskName:{},taskType:{},params:{}",                taskType, taskId, taskName, taskType, params);        //任务处理的方式:        //todo cc 1就在这里执行:模拟任务        //todo cc 2开启策略模式,根据任务类型 调度不同的任务        //todo cc 3使用反射:传来bean名字,方法名字,调用不同的任务        //todo cc 4开启队列,把要执行的任务放到队列中,然后执行 —— 使用场景:每个任务执行很耗时的情况下使用        try {            Thread.sleep(1000);        } catch (InterruptedException e) {            throw new RuntimeException(e);        }        log.info("\n\t {}号.任务执行完成 - 耗时:{},taskId:{},taskType:{}",                taskType, System.currentTimeMillis() - start, taskId, taskType);    }}
5、测试Controller
package com.cc.ssd.web.controller;import com.cc.ssd.registrar.CronTaskRegistrar;import com.cc.ssd.task.CronTaskRunnable;import org.springframework.web.bind.annotation.*;import javax.annotation.Resource;import java.util.List;import java.util.Map;/** * @author CC * @since 2023/4/21 0021 */@RestController@RequestMapping("/scheduled")public class TestScheduledController {    @Resource    private CronTaskRegistrar cronTaskRegistrar;    /** 获取任务列表     * @return java.util.List<com.cc.ssd.task.SchedulingRunnableTask>     * @since 2023/4/21 0021     * @author CC     **/    @GetMapping    public List<CronTaskRunnable> getScheduledTasks() {        return cronTaskRegistrar.getScheduledTasks();    }    /** 添加任务     * @param param param     * @return java.lang.String     * @since 2023/4/21 0021     * @author CC     **/    @PostMapping    public String addCronTask(@RequestBody Map<String, Object> param) {        //自己拿任务参数的逻辑:可以把每个任务保存到数据库,重新启动任务的同时,加载这些任务到任务调度中心        String taskId = (String) param.get("taskId");        Integer taskType = (Integer) param.get("taskType");        String taskName = (String) param.get("taskName");        Object params = param.get("params");        //添加任务参数        CronTaskRunnable task = new CronTaskRunnable(taskId, taskType, taskName, params);        //注册任务:cron表达式,可以从传入不一样的        cronTaskRegistrar.addCronTask(task, "0/5 * * * * ?");        return "ok";    }    /** 根据任务id删除定时任务     * @param taskId 任务id     * @return java.lang.String     * @since 2023/4/21 0021     * @author CC     **/    @DeleteMapping    public String removeCronTaskByTaskId(@RequestParam String taskId) {        cronTaskRegistrar.removeCronTaskByTaskId(taskId);        return "ok";    }    /** 删除全部任务     * @return java.lang.String     * @since 2023/4/21 0021     * @author CC     **/    @DeleteMapping("/removeAll")    public String removeCronTask() {        cronTaskRegistrar.destroy();        return "ok";    }}
6、最后效果自己用controller去测试一波吧

标签: #java自定义定时任务 #java自定义定时任务怎么做