2、定时器

Java实现定时器的方式

1、Timer:这是Java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务。使用这种方式可以让你的程序按照某一个频度执行,但不能在指定时间运行,一般使用比较少

2、Spring Task:Spring3.0以后自带的Task,可以将它看成一个轻量级的Quartz,而且使用起来比Quartz简单许多

3、ScheduledExecutorService:也是jdk自带的一个类,是基于线程池设计的定时任务类,每个调度任务都会分配到线程池中的一个线程去执行,也就是说,任务是并发执行的,互不影响

4、Quartz:这是一个功能比较强大的调度器,可以让你的程序在指定的时间执行,也可以按照某一个频度执行,配置起来稍显复杂

5、xxl-job:XXL-JOB是一个轻量级分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线,开箱即用。xxl三个字母是其开发者许雪里名字的缩写。

SpringScheduled

简单使用

1、添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

2、启动类添加注解,开启对定时任务的支持

@EnableScheduling

3、新建定时任务

@Component
public class TimingService {

    private int count=0;

    //使用cron表达式,每天23点执行一次
    @Scheduled(cron="0 0 23 * *")
    public void timingTask() throws Exception {
        System.out.println("this is scheduler task runing  "+(count++));
    }
}

动态编辑定时任务

1、添加执行定时任务的线程池配置类
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

/**
 * @描述 执行定时任务的线程池配置类
 * @创建人 yhgh
 * @创建时间 2022/8/26 14:01
 */
@Configuration
public class SchedulingConfig {
    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        // 定时任务执行线程池核心线程数
        taskScheduler.setPoolSize(4);
        taskScheduler.setRemoveOnCancelPolicy(true);
        taskScheduler.setThreadNamePrefix("TaskSchedulerThreadPool-");
        return taskScheduler;
    }
}
2、添加ScheduledFuture的包装类
import java.util.concurrent.ScheduledFuture;

/**
 * @描述 ScheduledFuture的包装类。ScheduledFuture是ScheduledExecutorService定时任务线程池的执行结果。
 * @创建人 yhgh
 * @创建时间 2022/8/26 14:03
 */
public final class ScheduledTask {

    public volatile ScheduledFuture<?> future;

    /**
     * 取消定时任务
     */
    public void cancel() {
        ScheduledFuture<?> future = this.future;
        if (future != null) {
            future.cancel(true);
        }
    }
}
3、SpringContextUtils用于从Spring容器中操作Bean
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * @描述 用于从Spring容器中操作Bean
 * @创建人 yhgh
 * @创建时间 2022/8/26 14:13
 */
@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);
    }
}
4、添加Runnable接口实现类,被定时任务线程池调用,用来执行指定bean里面的方法。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.util.Objects;

/**
 * @描述 Runnable接口实现类,被定时任务线程池调用,用来执行指定bean里面的方法。
 * @创建人 yhgh
 * @创建时间 2022/8/26 14:04
 */
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() {
        logger.info("定时任务开始执行 - bean:{},方法:{},参数:{}", beanName, methodName, params);
        long startTime = System.currentTimeMillis();

        try {
            Object target = SpringContextUtils.getBean(beanName);

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

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

        long times = System.currentTimeMillis() - startTime;
        logger.info("定时任务执行结束 - bean:{},方法:{},参数:{},耗时:{} 毫秒", beanName, methodName, params, times);
    }

    /**
     * 重写equals方法,使用beanName、methodName、params比较
     * @param o
     * @return
     */
    @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);
    }
}
5、添加定时任务注册类,用来增加、删除定时任务。
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.config.CronTask;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @描述 添加定时任务注册类,用来增加、删除定时任务。
 * @创建人 yhgh
 * @创建时间 2022/8/26 14:18
 */
@Component
public class CronTaskRegistrar implements DisposableBean {

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

    @Autowired
    private TaskScheduler taskScheduler;

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

    /**
     * 添加任务
     * @param task
     * @param cronExpression
     */
    public void addCronTask(Runnable task, String cronExpression) {
        addCronTask(new CronTask(task, cronExpression));
    }

    /**
     * 将定时任务添加进Map中
     * @param cronTask
     */
    private void addCronTask(CronTask cronTask) {
        if (cronTask != null) {
            Runnable task = cronTask.getRunnable();
            if (this.scheduledTasks.containsKey(task)) {
                removeCronTask(task);
            }

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

    /**
     * 删除任务
     * @param task
     */
    public void removeCronTask(Runnable task) {
        ScheduledTask scheduledTask = this.scheduledTasks.remove(task);
        if (scheduledTask != null)
            scheduledTask.cancel();
    }

    /**
     * 对cronTask进行包装,并执行定时任务
     * @param cronTask
     * @return
     */
    private 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();
    }
}
6、建表、编写Dao

image-20220826160558861

CREATE TABLE `system_task` (
    `task_id` varchar(70) NOT NULL COMMENT '任务id',
    `bean_name` varchar(100) DEFAULT NULL COMMENT 'bean名称',
    `method_name` varchar(100) DEFAULT NULL COMMENT '方法名称',
    `method_params` varchar(255) DEFAULT NULL COMMENT '方法参数',
    `cron_expression` varchar(255) DEFAULT NULL COMMENT 'cron表达式',
    `task_status` tinyint(1) DEFAULT NULL COMMENT '状态(1正常 0暂停)',
    `remark` varchar(500) DEFAULT NULL COMMENT '备注',
    `create_time` datetime DEFAULT NULL COMMENT '创建时间',
    `update_time` datetime DEFAULT NULL COMMENT '更新时间',
    PRIMARY KEY (`task_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='系统定时任务';
import java.util.Date;
import java.io.Serializable;

/**
 * 系统定时任务(SystemTask)实体类
 *
 * @since 2022-08-26 15:29:44
 */
public class SystemTask implements Serializable {
    private static final long serialVersionUID = -54922456361386370L;
    /**
     * 任务id
     */
    private String taskId;
    /**
     * bean名称
     */
    private String beanName;
    /**
     * 方法名称
     */
    private String methodName;
    /**
     * 方法参数
     */
    private String methodParams;
    /**
     * cron表达式
     */
    private String cronExpression;
    /**
     * 状态(1正常 0暂停)
     */
    private Boolean taskStatus;
    /**
     * 备注
     */
    private String remark;
    /**
     * 创建时间
     */
    private Date createTime;
    /**
     * 更新时间
     */
    private Date updateTime;


    public String getTaskId() {
        return taskId;
    }

    public void setTaskId(String taskId) {
        this.taskId = taskId;
    }

    public String getBeanName() {
        return beanName;
    }

    public void setBeanName(String beanName) {
        this.beanName = beanName;
    }

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    public String getMethodParams() {
        return methodParams;
    }

    public void setMethodParams(String methodParams) {
        this.methodParams = methodParams;
    }

    public String getCronExpression() {
        return cronExpression;
    }

    public void setCronExpression(String cronExpression) {
        this.cronExpression = cronExpression;
    }

    public Boolean getTaskStatus() {
        return taskStatus;
    }

    public void setTaskStatus(Boolean taskStatus) {
        this.taskStatus = taskStatus;
    }

    public String getRemark() {
        return remark;
    }

    public void setRemark(String remark) {
        this.remark = remark;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public Date getUpdateTime() {
        return updateTime;
    }

    public void setUpdateTime(Date updateTime) {
        this.updateTime = updateTime;
    }

}
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;

/**
 * 系统定时任务(SystemTask)表数据库访问层
 *
 * @since 2022-08-26 15:29:43
 */
@Mapper
public interface SystemTaskDao {

    /**
     * 通过ID查询单条数据
     *
     * @param taskId 主键
     * @return 实例对象
     */
    SystemTask queryById(String taskId);

    /**
     * 新增数据
     *
     * @param systemTask 实例对象
     * @return 影响行数
     */
    int insert(SystemTask systemTask);

    /**
     * 修改数据
     *
     * @param systemTask 实例对象
     * @return 影响行数
     */
    int update(SystemTask systemTask);

    /**
     * 通过主键删除数据
     *
     * @param taskId 主键
     * @return 影响行数
     */
    int deleteById(String taskId);

    /**
     * 根据任务状态查询所有任务
     * @param status
     * @return
     */
    List<SystemTask> selectTasksByStatus(@Param("taskStatus")Boolean status);

}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="top.ygang.task.dao.SystemTaskDao">

    <resultMap type="top.ygang.task.entity.SystemTask" id="SystemTaskMap">
        <result property="taskId" column="task_id" jdbcType="VARCHAR"/>
        <result property="beanName" column="bean_name" jdbcType="VARCHAR"/>
        <result property="methodName" column="method_name" jdbcType="VARCHAR"/>
        <result property="methodParams" column="method_params" jdbcType="VARCHAR"/>
        <result property="cronExpression" column="cron_expression" jdbcType="VARCHAR"/>
        <result property="taskStatus" column="task_status" jdbcType="TINYINT"/>
        <result property="remark" column="remark" jdbcType="VARCHAR"/>
        <result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
        <result property="updateTime" column="update_time" jdbcType="TIMESTAMP"/>
    </resultMap>

    <!--查询单个-->
    <select id="queryById" resultMap="SystemTaskMap">
        select
        task_id, bean_name, method_name, method_params, cron_expression, task_status, remark, create_time, update_time
        from system_task
        where task_id = #{taskId}
    </select>

    <!--新增所有列-->
    <insert id="insert">
        insert into system_task(bean_name, method_name, method_params, cron_expression, task_status, remark, create_time, update_time)
        values (#{beanName}, #{methodName}, #{methodParams}, #{cronExpression}, #{taskStatus}, #{remark}, #{createTime}, #{updateTime})
    </insert>

    <!--通过主键修改数据-->
    <update id="update">
        update system_task
        <set>
            <if test="beanName != null and beanName != ''">
                bean_name = #{beanName},
            </if>
            <if test="methodName != null and methodName != ''">
                method_name = #{methodName},
            </if>
            <if test="methodParams != null and methodParams != ''">
                method_params = #{methodParams},
            </if>
            <if test="cronExpression != null and cronExpression != ''">
                cron_expression = #{cronExpression},
            </if>
            <if test="taskStatus != null">
                task_status = #{taskStatus},
            </if>
            <if test="remark != null and remark != ''">
                remark = #{remark},
            </if>
            <if test="createTime != null">
                create_time = #{createTime},
            </if>
            <if test="updateTime != null">
                update_time = #{updateTime},
            </if>
        </set>
        where task_id = #{taskId}
    </update>

    <!--通过主键删除-->
    <delete id="deleteById">
        delete from system_task where task_id = #{taskId}
    </delete>

    <select id="selectTasksByStatus" resultMap="SystemTaskMap">
        select
        task_id, bean_name, method_name, method_params, cron_expression, task_status, remark, create_time, update_time
        from system_task
        where task_status = #{taskStatus}
    </select>

</mapper>
7、编写service
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

/**
 * @描述 系统定时任务业务层
 * @创建人 yhgh
 * @创建时间 2022/8/26 15:43
 */
@Service
public class SystemTaskService {

    @Autowired
    private SystemTaskDao systemTaskDao;
    @Autowired
    private CronTaskRegistrar cronTaskRegistrar;

    /**
     * 新增系统定时任务
     * @param systemTask
     * @return
     */
    public boolean addTask(SystemTask systemTask){
        if (!StringUtils.hasText(systemTask.getBeanName()) ||
            !StringUtils.hasText(systemTask.getMethodName()) ||
            !StringUtils.hasText(systemTask.getCronExpression())){
            return false;
        }
        systemTask.setTaskId(UUID.randomUUID().toString());
        Date now = new Date();
        systemTask.setCreateTime(now);
        systemTask.setUpdateTime(now);
        int insert = systemTaskDao.insert(systemTask);
        if (insert <= 0) {
            return false;
        } else {
            if (systemTask.getTaskStatus()) {
                SchedulingRunnable task = new SchedulingRunnable(systemTask.getBeanName(), systemTask.getMethodName(), systemTask.getMethodParams());
                cronTaskRegistrar.addCronTask(task, systemTask.getCronExpression());
            }
        }
        return true;
    }

    /**
     * 根据id更新系统定时任务
     * @param systemTask
     * @return
     */
    public boolean updateTask(SystemTask systemTask){
        SystemTask old = systemTaskDao.queryById(systemTask.getTaskId());
        systemTask.setUpdateTime(new Date());
        int update = systemTaskDao.update(systemTask);
        if (update <= 0) {
            return false;
        }
        // 删除老的定时任务
        SchedulingRunnable task = new SchedulingRunnable(old.getBeanName(), old.getMethodName(), old.getMethodParams());
        cronTaskRegistrar.removeCronTask(task);
        // 如果新定时任务状态为正常,开启新定时任务
        SystemTask newTask = systemTaskDao.queryById(systemTask.getTaskId());
        if (newTask.getTaskStatus()){
            SchedulingRunnable ta = new SchedulingRunnable(newTask.getBeanName(), newTask.getMethodName(), newTask.getMethodParams());
            cronTaskRegistrar.addCronTask(ta, newTask.getCronExpression());
        }
        return true;
    }

    /**
     * 根据id删除系统定时任务
     * @param systemTask
     * @return
     */
    public boolean removeTask(SystemTask systemTask){
        SystemTask ta = systemTaskDao.queryById(systemTask.getTaskId());
        int delete = systemTaskDao.deleteById(systemTask.getTaskId());
        if (delete <= 0)
            return false;
        else{
            if (ta.getTaskStatus()) {
                SchedulingRunnable task = new SchedulingRunnable(ta.getBeanName(), ta.getMethodName(), ta.getMethodParams());
                cronTaskRegistrar.removeCronTask(task);
            }
        }
        return true;
    }
}
8、添加实现了CommandLineRunner接口的类,当spring boot项目启动完成后,加载数据库里状态为正常的定时任务。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * @描述 项目启动完成后,加载数据库里状态为正常的定时任务
 * @创建人 yhgh
 * @创建时间 2022/8/26 15:22
 */
@Component
public class SystemTaskRunner implements CommandLineRunner {

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

    @Autowired
    private SystemTaskDao systemTaskDao;

    @Autowired
    private CronTaskRegistrar cronTaskRegistrar;

    @Override
    public void run(String... args) {
        // 初始加载数据库里状态为正常的定时任务
        List<SystemTask> taskList = systemTaskDao.selectTasksByStatus(true);
        if (CollectionUtils.isNotEmpty(taskList)) {
            for (SystemTask task : taskList) {
                SchedulingRunnable schedulingRunnable = new SchedulingRunnable(task.getBeanName(), task.getMethodName(), task.getMethodParams());
                cronTaskRegistrar.addCronTask(schedulingRunnable, task.getCronExpression());
            }

            logger.info("定时任务已加载完毕...");
        }
    }
}

cron表达式

可以百度搜索cron表达式生成

Cron表达式是一个字符串,字符串以5或6个空格隔开,分为6或7个域,每一个域代表一个含义

第一位:Seconds(秒)--可出现", - * /"四个字符,有效范围为0-59的整数 第二位:Minutes(分)--可出现", - * /"四个字符,有效范围为0-59的整数 第三位:Hours(时)--可出现", - * /"四个字符,有效范围为0-23的整数 第四位:DayofMonth(日)--可出现", - * / ? L W C"八个字符,有效范围为0-31的整数 第五位:Month(月)--可出现", - * /"四个字符,有效范围为1-12的整数或JAN-DEc 第六位:DayofWeek(星期)--可出现", - * / ? L C #"四个字符,有效范围为1-7的整数或SUN-SAT两个范围。1表示星期天,2表示星期一, 依次类推 第七位:Year(年)--可出现", - * /"四个字符,有效范围为1970-2099年

注意事项:
每一个域都使用数字,但还可以出现如下特殊字符,它们的含义是:
    (1)*:表示匹配该域的任意值。假如在Minutes域使用*, 即表示每分钟都会触发事件。
    (2)?:只能用在DayofMonth和DayofWeek两个域。它也匹配域的任意值,但实际不会。因为DayofMonth和DayofWeek会相互影响。例如想在每月的20日触发调度,不管20日到底是星期几,则只能使用如下写法: 13 13 15 20 * ?, 其中最后一位只能用?,而不能使用*,如果使用*表示不管星期几都会触发,实际上并不是这样。
    (3)-:表示范围。例如在Minutes域使用5-20,表示从5分到20分钟每分钟触发一次 
    (4)/:表示起始时间开始触发,然后每隔固定时间触发一次。例如在Minutes域使用5/20,则意味着5分钟触发一次,而25,45等分别触发一次. 
    (5),:表示列出枚举值。例如:在Minutes域使用5,20,则意味着在5和20分每分钟触发一次。 
    (6)L:表示最后,只能出现在DayofWeek和DayofMonth域。如果在DayofWeek域使用5L,意味着在最后的一个星期四触发。 
    (7)W:表示有效工作日(周一到周五),只能出现在DayofMonth域,系统将在离指定日期的最近的有效工作日触发事件。例如:在 DayofMonth使用5W,如果5日是星期六,则将在最近的工作日:星期五,即4日触发。如果5日是星期天,则在6日(周一)触发;如果5日在星期一到星期五中的一天,则就在5日触发。另外一点,W的最近寻找不会跨过月份 。
    (8)LW:这两个字符可以连用,表示在某个月最后一个工作日,即最后一个星期五。 
    (9)#:用于确定每个月第几个星期几,只能出现在DayofMonth域。例如在4#2,表示某月的第二个星期三。

常用表达式例子
    (1)0 0 2 1 * ? *   表示在每月的1日的凌晨2点调整任务
    (2)0 15 10 ? * MON-FRI   表示周一到周五每天上午10:15执行作业
    (3)0 15 10 ? 6L 2002-2006   表示2002-2006年的每个月的最后一个星期五上午10:15执行作
    (4)0 0 10,14,16 * * ?   每天上午10点,下午2点,4点 
    (5)0 0/30 9-17 * * ?   朝九晚五工作时间内每半小时 
    (6)0 0 12 ? * WED    表示每个星期三中午12点 
    (7)0 0 12 * * ?   每天中午12点触发 
    (8)0 15 10 ? * *    每天上午10:15触发 
    (9)0 15 10 * * ?     每天上午10:15触发 
    (10)0 15 10 * * ? *    每天上午10:15触发 
    (11)0 15 10 * * ? 2005    2005年的每天上午10:15触发 
    (12)0 * 14 * * ?     在每天下午2点到下午2:59期间的每1分钟触发 
    (13)0 0/5 14 * * ?    在每天下午2点到下午2:55期间的每5分钟触发 
    (14)0 0/5 14,18 * * ?     在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发 
    (15)0 0-5 14 * * ?    在每天下午2点到下午2:05期间的每1分钟触发 
    (16)0 10,44 14 ? 3 WED    每年三月的星期三的下午2:10和2:44触发 
    (17)0 15 10 ? * MON-FRI    周一至周五的上午10:15触发 
    (18)0 15 10 15 * ?    每月15日上午10:15触发 
    (19)0 15 10 L * ?    每月最后一日的上午10:15触发 
    (20)0 15 10 ? * 6L    每月的最后一个星期五上午10:15触发 
    (21)0 15 10 ? * 6L 2002-2005   2002年至2005年的每月的最后一个星期五上午10:15触发 
    (22)0 15 10 ? * 6#3   每月的第三个星期五上午10:15触发

Quartz

传统的定时任务的缺点就是,如果进行横向扩展为多实例集群部署时,遇到了问题:定时任务在多个应用实例中重复执行了。此时quartz可以很好的解决分布式集群任务调度的问题,使得无论集群中有多少应用实例,定时任务只会触发一次

Scheduler:与quartz schedule交互的主要api Job:Scheduler执行组件需要实现的接口(你要做什么事) JobDetail:用于定义实现了Job接口的实例 Trigger:用于执行给定作业的计划的组件(你什么时候去做) JobBuilder:用于构建JobDetail实例,或者说定义Job的实例 TriggerBuilder:用于构建触发器实例

Quartz 官方提供了11张数据表,这里使用mysql(innodb)的数据表,也有其他数据库的表,可以到官网(http://www.quartz-scheduler.org/downloads/)下载

1、建表

#
# 在Quartz属性文件中,需要设置
# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate

DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
DROP TABLE IF EXISTS QRTZ_LOCKS;
DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
DROP TABLE IF EXISTS QRTZ_CALENDARS;

CREATE TABLE QRTZ_JOB_DETAILS(
SCHED_NAME VARCHAR(120) NOT NULL COMMENT '调度名称',
JOB_NAME VARCHAR(190) NOT NULL COMMENT '集群中job的名字',
JOB_GROUP VARCHAR(190) NOT NULL COMMENT '集群中job的所属组的名字',
DESCRIPTION VARCHAR(250) NULL COMMENT '详细描述信息',
JOB_CLASS_NAME VARCHAR(250) NOT NULL COMMENT '集群中个notejob实现类的全限定名,quartz就是根据这个路径到classpath找到该job类',
IS_DURABLE VARCHAR(1) NOT NULL COMMENT '是否持久化,把该属性设置为1,quartz会把job持久化到数据库中',
IS_NONCONCURRENT VARCHAR(1) NOT NULL COMMENT '是否并发执行',
IS_UPDATE_DATA VARCHAR(1) NOT NULL COMMENT '是否更新数据',
REQUESTS_RECOVERY VARCHAR(1) NOT NULL COMMENT '是否接受恢复执行,默认为false,设置了RequestsRecovery为true,则该job会被重新执行',
JOB_DATA BLOB NULL COMMENT '一个blob字段,存放持久化job对象',
PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP))
ENGINE=InnoDB COMMENT='已配置的jobDetail的详细信息';

CREATE TABLE QRTZ_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL COMMENT '调度名称',
TRIGGER_NAME VARCHAR(190) NOT NULL COMMENT '触发器的名字',
TRIGGER_GROUP VARCHAR(190) NOT NULL COMMENT '触发器所属组的名字',
JOB_NAME VARCHAR(190) NOT NULL COMMENT 'qrtz_job_details表job_name的外键',
JOB_GROUP VARCHAR(190) NOT NULL COMMENT 'qrtz_job_details表job_group的外键',
DESCRIPTION VARCHAR(250) NULL COMMENT '详细描述信息',
NEXT_FIRE_TIME BIGINT(13) NULL COMMENT '上一次触发时间(毫秒)',
PREV_FIRE_TIME BIGINT(13) NULL COMMENT '下一次触发时间,默认为-1,意味不会自动触发',
PRIORITY INTEGER NULL COMMENT '优先级',
TRIGGER_STATE VARCHAR(16) NOT NULL COMMENT '当前触发器状态,设置为ACQUIRED,如果设置为WAITING,则job不会触发 ( WAITING:等待 PAUSED:暂停ACQUIRED:正常执行 BLOCKED:阻塞 ERROR:错误)',
TRIGGER_TYPE VARCHAR(8) NOT NULL COMMENT '触发器的类型,使用cron表达式',
START_TIME BIGINT(13) NOT NULL COMMENT '开始时间',
END_TIME BIGINT(13) NULL COMMENT '结束时间',
CALENDAR_NAME VARCHAR(190) NULL COMMENT '日程表名称,表qrtz_calendars的calendar_name字段外键',
MISFIRE_INSTR SMALLINT(2) NULL COMMENT '措施或者是补偿执行的策略',
JOB_DATA BLOB NULL COMMENT '一个blob字段,存放持久化job对象',
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP))
ENGINE=InnoDB COMMENT='触发器的基本信息';

CREATE TABLE QRTZ_SIMPLE_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL COMMENT '调度名称',
TRIGGER_NAME VARCHAR(190) NOT NULL COMMENT 'qrtz_triggers表trigger_ name的外键',
TRIGGER_GROUP VARCHAR(190) NOT NULL COMMENT 'qrtz_triggers表trigger_group的外键',
REPEAT_COUNT BIGINT(7) NOT NULL COMMENT '重复的次数统计',
REPEAT_INTERVAL BIGINT(12) NOT NULL COMMENT '重复的间隔时间',
TIMES_TRIGGERED BIGINT(10) NOT NULL COMMENT '已经触发的次数',
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB COMMENT='简单的 Trigger';

CREATE TABLE QRTZ_CRON_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL COMMENT '调度名称',
TRIGGER_NAME VARCHAR(190) NOT NULL COMMENT 'qrtz_triggers表trigger_name的外键',
TRIGGER_GROUP VARCHAR(190) NOT NULL COMMENT 'qrtz_triggers表trigger_group的外键',
CRON_EXPRESSION VARCHAR(120) NOT NULL COMMENT 'cron表达式',
TIME_ZONE_ID VARCHAR(80) NULL COMMENT '时区',
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB COMMENT='触发器的cron表达式';

CREATE TABLE QRTZ_SIMPROP_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL COMMENT '调度名称',
    TRIGGER_NAME VARCHAR(190) NOT NULL COMMENT 'qrtz_triggers表trigger_ name的外键',
    TRIGGER_GROUP VARCHAR(190) NOT NULL COMMENT 'qrtz_triggers表trigger_group的外键',
    STR_PROP_1 VARCHAR(512) NULL COMMENT 'String类型的trigger的第一个参数',
    STR_PROP_2 VARCHAR(512) NULL COMMENT 'String类型的trigger的第二个参数',
    STR_PROP_3 VARCHAR(512) NULL COMMENT 'String类型的trigger的第三个参数',
    INT_PROP_1 INT NULL COMMENT 'int类型的trigger的第一个参数',
    INT_PROP_2 INT NULL COMMENT 'int类型的trigger的第二个参数',
    LONG_PROP_1 BIGINT NULL COMMENT 'long类型的trigger的第一个参数',
    LONG_PROP_2 BIGINT NULL COMMENT 'long类型的trigger的第二个参数',
    DEC_PROP_1 NUMERIC(13,4) NULL COMMENT 'decimal类型的trigger的第一个参数',
    DEC_PROP_2 NUMERIC(13,4) NULL COMMENT 'decimal类型的trigger的第二个参数',
    BOOL_PROP_1 VARCHAR(1) NULL COMMENT 'Boolean类型的trigger的第一个参数',
    BOOL_PROP_2 VARCHAR(1) NULL COMMENT 'Boolean类型的trigger的第二个参数',
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
    REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB COMMENT='存储CalendarIntervalTrigger和DailyTimeIntervalTrigger';

CREATE TABLE QRTZ_BLOB_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL COMMENT '调度名称',
TRIGGER_NAME VARCHAR(190) NOT NULL COMMENT 'qrtz_triggers表trigger_name的外键',
TRIGGER_GROUP VARCHAR(190) NOT NULL COMMENT 'qrtz_triggers表trigger_group的外键',
BLOB_DATA BLOB NULL COMMENT '一个blob字段,存放持久化Trigger对象',
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
INDEX (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB COMMENT='Trigger作为 Blob 类型存储(用于 Quartz 用户用 JDBC 创建他们自己定制的 Trigger 类型,JobStore 并不知道如何存储实例的时候)';

CREATE TABLE QRTZ_CALENDARS (
SCHED_NAME VARCHAR(120) NOT NULL COMMENT '调度名称',
CALENDAR_NAME VARCHAR(190) NOT NULL COMMENT '日历名称',
CALENDAR BLOB NOT NULL COMMENT '一个blob字段,存放持久化calendar对象',
PRIMARY KEY (SCHED_NAME,CALENDAR_NAME))
ENGINE=InnoDB COMMENT='以 Blob 类型存储存放日历信息,quartz可配置一个日历来指定一个时间范围';

CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS (
SCHED_NAME VARCHAR(120) NOT NULL COMMENT '调度名称',
TRIGGER_GROUP VARCHAR(190) NOT NULL COMMENT 'qrtz_triggers表trigger_group的外键',
PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP))
ENGINE=InnoDB COMMENT='存储已暂停的 Trigger 组的信息';

CREATE TABLE QRTZ_FIRED_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL COMMENT '调度名称',
ENTRY_ID VARCHAR(95) NOT NULL COMMENT '调度器实例id',
TRIGGER_NAME VARCHAR(190) NOT NULL COMMENT 'qrtz_triggers表trigger_name的外键',
TRIGGER_GROUP VARCHAR(190) NOT NULL COMMENT 'qrtz_triggers表trigger_group的外键',
INSTANCE_NAME VARCHAR(190) NOT NULL COMMENT '调度器实例名',
FIRED_TIME BIGINT(13) NOT NULL COMMENT '触发的时间',
SCHED_TIME BIGINT(13) NOT NULL COMMENT '定时器制定的时间',
PRIORITY INTEGER NOT NULL COMMENT '优先级',
STATE VARCHAR(16) NOT NULL COMMENT '状态',
JOB_NAME VARCHAR(190) NULL COMMENT '集群中job的名字',
JOB_GROUP VARCHAR(190) NULL COMMENT '集群中job的所属组的名字',
IS_NONCONCURRENT VARCHAR(1) NULL COMMENT '是否并发',
REQUESTS_RECOVERY VARCHAR(1) NULL COMMENT '是否接受恢复执行,默认为false,设置了RequestsRecovery为true,则会被重新执行',
PRIMARY KEY (SCHED_NAME,ENTRY_ID))
ENGINE=InnoDB COMMENT='存储与已触发的 Trigger 相关的状态信息,以及相联 Job 的执行信息';

CREATE TABLE QRTZ_SCHEDULER_STATE (
SCHED_NAME VARCHAR(120) NOT NULL COMMENT '调度名称',
INSTANCE_NAME VARCHAR(190) NOT NULL COMMENT '之前配置文件中org.quartz.scheduler.instanceId配置的名字,就会写入该字段',
LAST_CHECKIN_TIME BIGINT(13) NOT NULL COMMENT '上次检查时间',
CHECKIN_INTERVAL BIGINT(13) NOT NULL COMMENT '检查间隔时间',
PRIMARY KEY (SCHED_NAME,INSTANCE_NAME))
ENGINE=InnoDB COMMENT='存储集群中note实例信息,quartz会定时读取该表的信息判断集群中每个实例的当前状态';

CREATE TABLE QRTZ_LOCKS (
SCHED_NAME VARCHAR(120) NOT NULL COMMENT '调度名称',
LOCK_NAME VARCHAR(40) NOT NULL COMMENT '悲观锁名称',
PRIMARY KEY (SCHED_NAME,LOCK_NAME))
ENGINE=InnoDB COMMENT='存储程序的悲观锁的信息(假如使用了悲观锁)';

CREATE INDEX IDX_QRTZ_J_REQ_RECOVERY ON QRTZ_JOB_DETAILS(SCHED_NAME,REQUESTS_RECOVERY);
CREATE INDEX IDX_QRTZ_J_GRP ON QRTZ_JOB_DETAILS(SCHED_NAME,JOB_GROUP);

CREATE INDEX IDX_QRTZ_T_J ON QRTZ_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_T_JG ON QRTZ_TRIGGERS(SCHED_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_T_C ON QRTZ_TRIGGERS(SCHED_NAME,CALENDAR_NAME);
CREATE INDEX IDX_QRTZ_T_G ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);
CREATE INDEX IDX_QRTZ_T_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_N_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_N_G_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_NEXT_FIRE_TIME ON QRTZ_TRIGGERS(SCHED_NAME,NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_ST ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE,NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE_GRP ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_GROUP,TRIGGER_STATE);

CREATE INDEX IDX_QRTZ_FT_TRIG_INST_NAME ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME);
CREATE INDEX IDX_QRTZ_FT_INST_JOB_REQ_RCVRY ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME,REQUESTS_RECOVERY);
CREATE INDEX IDX_QRTZ_FT_J_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_FT_JG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_FT_T_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP);
CREATE INDEX IDX_QRTZ_FT_TG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);

commit;

2、项目中引入依赖

<!--springboot依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--quartz依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<!--mysql-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.15</version>
</dependency>
<!--druid-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.2.6</version>
</dependency>

3、application.yml配置文件

server:
  port: 18000

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
    url: jdbc:mysql://127.0.0.1:3306/quartz-test?characterEncoding=utf8&useSSL=false&autoReconnect=true&serverTimezone=Asia/Shanghai
    type: com.alibaba.druid.pool.DruidDataSource
    #数据库连接池
    druid:
      # 初始化时建立物理连接的个数
      initial-size: 10
      # 最小连接池数量
      min-idle: 5
      # 最大连接池数量
      max-active: 20
      # 获取连接时最大等待时间,单位毫秒
      max-wait: 60000
      # 申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
      test-while-idle: true
      # 既作为检测的间隔时间又作为testWhileIdel执行的依据
      time-between-eviction-runs-millis: 60000
      #销毁线程时检测当前连接的最后活动时间和当前时间差大于该值时,关闭当前连接
      min-evictable-idle-time-millis: 30000
  # 使用jdbc的方式持久化定时任务
  quartz:
    job-store-type: jdbc

4、TaskInfo实体类,为了传参方便

package top.ygang.quartztest.quartz;

import org.quartz.JobDataMap;

import java.util.Date;

/**
 * @描述 任务信息
 * @创建人 yhgh
 */
public class TaskInfo {
    /**
     * 任务名称
     */
    private String jobName;
    /**
     * 任务组
     */
    private String jobGroup;
    /**
     * 任务描述
     */
    private String description;
    /**
     * Job实现类全类名
     */
    private String className;
    /**
     * 触发器名称
     */
    private String triggerName;
    /**
     * 触发器组
     */
    private String triggerGroup;
    /**
     * cron表达式
     */
    private String cron;
    /**
     * 创建时间
     */
    private Date createTime;
    /**
     * execute的执行参数
     */
    private JobDataMap jobDataMap;
    /**
     * 任务状态
     */
    private String state;

    public String getJobName() {
        return jobName;
    }

    public void setJobName(String jobName) {
        this.jobName = jobName;
    }

    public String getJobGroup() {
        return jobGroup;
    }

    public void setJobGroup(String jobGroup) {
        this.jobGroup = jobGroup;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }

    public String getTriggerName() {
        return triggerName;
    }

    public void setTriggerName(String triggerName) {
        this.triggerName = triggerName;
    }

    public String getTriggerGroup() {
        return triggerGroup;
    }

    public void setTriggerGroup(String triggerGroup) {
        this.triggerGroup = triggerGroup;
    }

    public String getCron() {
        return cron;
    }

    public void setCron(String cron) {
        this.cron = cron;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public JobDataMap getJobDataMap() {
        return jobDataMap;
    }

    public void setJobDataMap(JobDataMap jobDataMap) {
        this.jobDataMap = jobDataMap;
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }
}

5、业务层,处理各种关于定时任务的业务

package top.ygang.quartztest.quartz;

import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.*;

/**
 * @描述 quartz业务层
 * @创建人 yhgh
 */
@Service
public class QuartzService {

    Logger logger = LoggerFactory.getLogger(QuartzService.class);

    @Autowired
    private Scheduler scheduler;

    /**
     * 获取所有任务列表
     * @return
     */
    public List<TaskInfo> getJobList() {
        List<TaskInfo> list = new ArrayList<>();
        try {

            List<String> jobGroupNames = scheduler.getJobGroupNames();
            for (String groupJob : jobGroupNames) {
                for (JobKey jobKey : scheduler.getJobKeys(GroupMatcher.<JobKey>groupEquals(groupJob))) {
                    List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
                    Trigger trigger = triggers.get(0);
                    JobDetail jobDetail = scheduler.getJobDetail(jobKey);
                    TriggerKey triggerKey = trigger.getKey();
                    String state = scheduler.getTriggerState(trigger.getKey()).name();
                    String jName = jobKey.getName();
                    String jGroup = jobKey.getGroup();
                    String tGroup = triggerKey.getGroup();
                    String tName = triggerKey.getName();
                    CronTrigger cronTrigger = (CronTrigger) trigger;
                    String cron = cronTrigger.getCronExpression();
                    String description = jobDetail.getDescription();
                    String className = jobDetail.getJobClass().getName();
                    JobDataMap jobDataMap = jobDetail.getJobDataMap();
                    Date createTime = (Date) jobDataMap.get("createTime");
                    jobDataMap.remove("createTime");
                    TaskInfo info = new TaskInfo();
                    info.setJobName(jName);
                    info.setJobGroup(jGroup);
                    info.setClassName(className);
                    info.setDescription(description);
                    info.setCron(cron);
                    info.setTriggerName(tName);
                    info.setTriggerGroup(tGroup);
                    info.setCreateTime(createTime);
                    info.setJobDataMap(jobDataMap);
                    info.setState(state);
                    list.add(info);
                }
            }
        }catch (Exception e){
            logger.error("获取任务列表失败",e);
            return list;
        }
        return list;
    }

    /**
     * 新增定时任务
     *
     * @param taskInfo
     */
    public void addJob(TaskInfo taskInfo) {
        try {
            // 根据传入的类名加载Job实现类
            Class<? extends Job> jobClass = (Class<? extends Job>) Class.forName(taskInfo.getClassName());
            // 将创建时间放到参数中保存
            taskInfo.getJobDataMap().put("createTime",new Date());
            // 构建JobDetail
            JobDetail jobDetail = JobBuilder.newJob(jobClass)
                    .withIdentity(taskInfo.getJobName(), taskInfo.getJobGroup())
                    .withDescription(taskInfo.getDescription())
                    .usingJobData(taskInfo.getJobDataMap())
                    .build();
            // 按新的cronExpression表达式构建一个新的trigger
            CronTrigger trigger = TriggerBuilder.newTrigger()
                    // 此处默认使用任务名称、组名作为触发器名,也可以自定义
                    .withIdentity(taskInfo.getJobName(), taskInfo.getJobGroup())
                    .startNow()
                    .withSchedule(CronScheduleBuilder.cronSchedule(taskInfo.getCron()))
                    .build();
            // 启动调度器
            scheduler.start();
            scheduler.scheduleJob(jobDetail, trigger);
        } catch (Exception e) {
            logger.error("创建定时任务失败",e);
        }
    }

    /**
     * 暂停定时任务
     * @param taskInfo
     * @throws SchedulerException
     */
    public void pauseJob(TaskInfo taskInfo) throws SchedulerException {
        scheduler.pauseJob(JobKey.jobKey(taskInfo.getJobName(), taskInfo.getJobGroup()));
    }

    /**
     * 开始定时任务
     * @param taskInfo
     * @throws SchedulerException
     */
    public void resumeJob(TaskInfo taskInfo) throws SchedulerException {
        scheduler.resumeJob(JobKey.jobKey(taskInfo.getJobName(), taskInfo.getJobGroup()));
    }

    /**
     * 更新任务
     * 注意:只能用来更新cron
     * @param taskInfo
     * @throws SchedulerException
     */
    public void rescheduleJob(TaskInfo taskInfo) throws SchedulerException {
        String jobName = taskInfo.getJobName();
        String jobGroup = taskInfo.getJobGroup();
        String description = taskInfo.getDescription();
        String cron = taskInfo.getCron();
        JobDataMap jobDataMap = taskInfo.getJobDataMap();
        TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
        JobKey jobKey = new JobKey(jobName, jobGroup);
        Trigger trigger = scheduler.getTrigger(triggerKey);
        // 如果修改cron
        if (StringUtils.hasText(cron)){
            CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder
                    .cronSchedule(cron)
                    .withMisfireHandlingInstructionDoNothing();
            trigger = TriggerBuilder.newTrigger()
                    .withIdentity(triggerKey)
                    .withSchedule(cronScheduleBuilder).build();
        }
        JobDetail jobDetail = scheduler.getJobDetail(jobKey);
        JobBuilder jobBuilder = jobDetail.getJobBuilder();
        // 如果修改描述
        if (StringUtils.hasText(description)){
            jobBuilder = jobBuilder.withDescription(description);
        }
        // 如果修改参数
        if (jobDataMap != null && jobDataMap.size() > 0){
            Date createTime = (Date) jobDetail.getJobDataMap().get("createTime");
            jobDataMap.put("createTime",createTime);
            jobBuilder = jobBuilder.usingJobData(jobDataMap);
        }
        jobDetail = jobBuilder.build();
        Set<Trigger> triggerSet = new HashSet<>();
        triggerSet.add(trigger);
        scheduler.scheduleJob(jobDetail, triggerSet,true);
    }

    /**
     * 删除定时任务
     * @param taskInfo
     * @throws SchedulerException
     */
    public void deleteJob(TaskInfo taskInfo) throws SchedulerException {
        scheduler.pauseTrigger(TriggerKey.triggerKey(taskInfo.getJobName(), taskInfo.getJobGroup()));
        scheduler.unscheduleJob(TriggerKey.triggerKey(taskInfo.getJobName(), taskInfo.getJobGroup()));
        scheduler.deleteJob(JobKey.jobKey(taskInfo.getJobName(), taskInfo.getJobGroup()));
    }
}

6、控制层

package top.ygang.quartztest.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import top.ygang.quartztest.quartz.QuartzService;
import top.ygang.quartztest.quartz.TaskInfo;

import java.util.List;

/**
 * @描述
 * @创建人 yhgh
 */
@RestController
public class QuartzController {

    @Autowired
    private QuartzService quartzService;

    /**
     * 新增定时任务
     * @param taskInfo
     * @return
     */
    @PostMapping("/addJob")
    public String addJob(@RequestBody TaskInfo taskInfo){
        try{
            quartzService.addJob(taskInfo);
            return "success";
        }catch (Exception e){
            e.printStackTrace();
            return "failed";
        }
    }

    /**
     * 删除定时任务
     * @param taskInfo
     * @return
     */
    @PostMapping("/deleteJob")
    public String deleteJob(@RequestBody TaskInfo taskInfo){
        try{
            quartzService.deleteJob(taskInfo);
            return "success";
        }catch (Exception e){
            e.printStackTrace();
            return "failed";
        }
    }

    /**
     * 更新定时任务
     * @param taskInfo
     * @return
     */
    @PostMapping("/rescheduleJob")
    public String rescheduleJob(@RequestBody TaskInfo taskInfo){
        try{
            quartzService.rescheduleJob(taskInfo);
            return "success";
        }catch (Exception e){
            e.printStackTrace();
            return "failed";
        }
    }

    /**
     * 暂停定时任务
     * @param taskInfo
     * @return
     */
    @PostMapping("/pauseJob")
    public String pauseJob(@RequestBody TaskInfo taskInfo){
        try{
            quartzService.pauseJob(taskInfo);
            return "success";
        }catch (Exception e){
            e.printStackTrace();
            return "failed";
        }
    }

    /**
     * 开始定时任务
     * @param taskInfo
     * @return
     */
    @PostMapping("/resumeJob")
    public String resumeJob(@RequestBody TaskInfo taskInfo){
        try{
            quartzService.resumeJob(taskInfo);
            return "success";
        }catch (Exception e){
            e.printStackTrace();
            return "failed";
        }
    }


    /**
     * 查询定时任务列表
     * @return
     */
    @PostMapping("/getJobList")
    public List<TaskInfo> getJobList(){
        try{
            List<TaskInfo> jobList = quartzService.getJobList();
            return jobList;
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }

}

7、定义一个定时任务

package top.ygang.quartztest.quartz.task;

import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

/**
 * @描述
 * @创建人 yhgh
 */
public class TestTask implements Job {
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
        String msg = (String) jobDataMap.get("msg");
        System.out.println("执行定时任务 : " + msg);
    }
}