1、简介

Spring Boot 内置注解方式实现的定时任务,在一定程度上也能解决一定的业务场景问题,但是若做更复杂的动作,例如启停任务、删除任务等等操作,实现起来则稍显复杂,此时便可以通过集成开源任务框架来实现。
常见的定时任务框架有 Quartz、elastic-job、xxl-job等等。
Quartz 存储方式有两种:MEMORY 和 JDBC。默认是内存形式维护任务信息,意味着服务重启了任务就从头再来,就像喝酒断片了一样;而 JDBC 形式就是能够把任务信息持久化到数据库,虽然服务重启了,依然还能接着来。
Quartz 提供了单机版和集群版,默认就是单机版。

2、使用内存方式

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- 引入 Quartz 依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

2.1、定义任务类

可以通过实现 Job 接口来定义任务,也可以通过继承 QuartzJobBean 这个抽象类来定义任务,其实 QuartzJobBean 本身也实现了 Job 接口,其本质都是实现 Job 接口来定义任务。
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;

/**
 * QuartzJobBean
 * Job 的实例要到该执行它们的时候才会实例化出来。每次 Job 被执行,一个新的 Job 实例会被创建。
 * 其中暗含的意思就是你的 Job 不必担心线程安全性,因为同一时刻仅有一个线程去执行给定 Job 类的实例,甚至是并发执行同一 Job 也是如此。
 * @DisallowConcurrentExecution 保证上一个任务执行完后,再去执行下一个任务
 */

/**
 * 定义任务方式一
 */
public class HelloJob1 extends QuartzJobBean {
    private static final Log logger = LogFactory.getLog(HelloJob1.class);

    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        logger.info("HelloJob1*************************************");
    }
}
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

import java.io.Serializable;
/**
 * 定义任务方式二
 */
public class HelloJob2 implements Job, Serializable {
    private static final Log logger = LogFactory.getLog(HelloJob2.class);

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        logger.info("HelloJob2*************************************");
    }
}

2.2、 定义任务描述及任务触发规则


import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import top.lrshuai.timer.job.HelloJob1;

@Configuration
public class QuartzConfig {

    @Bean
    public JobDetail jobDetail() {
        //指定任务描述具体的实现类
        return JobBuilder.newJob(HelloJob1.class)
                // 指定任务的名称
                .withIdentity("HelloJob1")
                // 任务描述
                .withDescription("任务描述:测试1")
                // 每次任务执行后进行存储
                .storeDurably()
                .build();
    }

    @Bean
    public Trigger trigger() {
        //创建触发器
        return TriggerBuilder.newTrigger()
##                 // 绑定工作任务
                .forJob(jobDetail())
                // 每隔 5 秒执行一次 job 固定间隔方式
                .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5))
                //cron表达式方式
                //.withSchedule(CronScheduleBuilder.cronSchedule("*/5 * * * * ?"))
                .build();
    }
}

3、 数据库方式存储任务信息

3.1、引入依赖

<dependency>
    <groupId>com.mchange</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.5.4</version>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

3.2、添加 Quartz 配置信息

spring:
  datasource:
    quartz:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/quartz?serverTimezone=GMT%2B8
      username: root
      password: 123456
  quartz:
    job-store-type: jdbc # 使用数据库存储
    scheduler-name: hyhScheduler # 相同 Scheduler 名字的节点,形成一个 Quartz 集群
    wait-for-jobs-to-complete-on-shutdown: true # 应用关闭时,是否等待定时任务执行完成。默认为 false ,建议设置为 true
    jdbc:
      initialize-schema: never # 是否自动使用 SQL 初始化 Quartz 表结构。这里设置成 never ,我们手动创建表结构。
    properties:
      org:
        quartz:
          # JobStore 相关配置
          jobStore:
            dataSource: quartzDataSource # 使用的数据源
            class: org.quartz.impl.jdbcjobstore.JobStoreTX # JobStore 实现类
            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
            tablePrefix: QRTZ_ # Quartz 表前缀
            isClustered: true # 是集群模式
            clusterCheckinInterval: 1000
            useProperties: false
          # 线程池相关配置
          threadPool:
            threadCount: 25 # 线程池大小。默认为 10 。
            threadPriority: 5 # 线程优先级
            class: org.quartz.simpl.SimpleThreadPool # 线程池类型
spring:
# 将 Quartz 持久化方式修改为 jdbc
  quartz:
    job-store-type: jdbc
  datasource:
    sql-script-encoding: UTF-8
    driverClassName: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/quartz?useUnicode=true&characterEncoding=utf-8&useSSL=false&autoReconnect=true&serverTimezone=Hongkong
    username: root
    password: 123456

3.3 初始化 Quartz 数据表信息

image-1654848038102

3.4、创建任务

@Component
public class JobInit implements ApplicationRunner {

    private static final String ID = "SUMMERDAY";

    @Autowired
    private Scheduler scheduler;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        JobDetail jobDetail = JobBuilder.newJob(SecondJob.class)
                .withIdentity(ID + " 02")
                .storeDurably()
                .build();
        CronScheduleBuilder scheduleBuilder =
                CronScheduleBuilder.cronSchedule("0/10 * * * * ? *");
        // 创建任务触发器
        Trigger trigger = TriggerBuilder.newTrigger()
                .forJob(jobDetail)
                .withIdentity(ID + " 02Trigger")
                .withSchedule(scheduleBuilder)
                .startNow() //立即執行一次任務
                .build();
        Set<Trigger> set = new HashSet<>();
        set.add(trigger);
        // boolean replace 表示启动时对数据库中的quartz的任务进行覆盖。
        scheduler.scheduleJob(jobDetail, set, true);
    }
}

4、任务动态管理

4.1、实体类配置

Quartz

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("task_quartz")
public class Quartz implements Serializable {

    private static final long serialVersionUID = 1L;

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

    /**
     * 任务分组
     */
    private String jobGroup;

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

    /**
     * 任务描述
     */
    private String description;

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

    /**
     * 任务执行时调用哪个类的方法 包名+类名
     */
    private String jobClassName;

    /**
     * 任务状态
     */
    private String jobStatus;

    /**
     * 创建者
     */
    private String createBy;

    /**
     * 创建时间
     */
    private LocalDateTime createTime;

    /**
     * 更新者
     */
    private String modifyBy;

    /**
     * 更新时间
     */
    private LocalDateTime modifyTime;

    @TableField(exist = false)
    private String oldJobName;//任务名称 用于修改
    @TableField(exist = false)
    private String oldJobGroup;//任务分组 用于修改
}

JobStatus

public enum JobStatus {
    /**
     *     STATE_BLOCKED("4"),
     */
    RUN("1"),
    PAUSED("2"),
    STOP("3");
    private String status;

    private JobStatus(String status){
        this.status=status;
    }

    public String getStatus() {
        return status;
    }
}

4.2、定时任务请求

QuartzController


import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.quartz.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import top.lrshuai.timer.common.constant.ApiResultEnum;
import top.lrshuai.timer.common.constant.Result;
import top.lrshuai.timer.task.entity.JobStatus;
import top.lrshuai.timer.task.entity.Quartz;
import top.lrshuai.timer.task.entity.QuartzDTO;
import top.lrshuai.timer.task.service.IQuartzService;

import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;
import java.util.List;

/**
 * <p>
 *  前端控制器
 * </p>
 *
 * @author rstyro
 * @since 2019-02-26
 */
@RestController
@RequestMapping("/task/quartz")
public class QuartzController {

    private  Logger LOGGER = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private Scheduler scheduler;

    @Autowired
    private IQuartzService quartzService;

    @PostMapping("/add")
    public Result save(Quartz quartz){
        LOGGER.info("新增任务");
        try {
            List<Quartz> list = quartzService.list(new QueryWrapper<Quartz>().lambda().eq(Quartz::getJobGroup, quartz.getJobGroup()).eq(Quartz::getJobName, quartz.getJobName()));
            if(list != null && list.size() > 0){
                return Result.error(ApiResultEnum.TRIGGER_GROUP_AND_NAME_SAME);
            }
            System.out.println("Quartz="+ JSON.toJSONString(quartz));
            quartz.setCreateTime(LocalDateTime.now());
            quartz.setJobStatus(JobStatus.STOP.getStatus());
            quartzService.save(quartz);


        } catch (Exception e) {
            e.printStackTrace();
            return Result.error();
        }
        return Result.ok();
    }

    @GetMapping("/list")
    public Result list(QuartzDTO dto){
        LOGGER.info("任务列表");
        return Result.ok(quartzService.getQuartzPage(dto));
    }

    @PostMapping("/start")
    public Result start(Long id) throws Exception {
        LOGGER.info("任务列表");
        Quartz quartz = quartzService.getById(id);
        if(quartz.getJobStatus().equals(JobStatus.RUN.getStatus())){
            return Result.error(ApiResultEnum.TASK_IS_RUNING);
        }else if(quartz.getJobStatus().equals(JobStatus.PAUSED.getStatus())){
            return Result.error(ApiResultEnum.TASK_IS_PAUSE);
        }
        addJob(quartz);
        quartz.setJobStatus(JobStatus.RUN.getStatus());
        quartzService.updateById(quartz);
        return Result.ok();
    }

    /**
     * 启动任务
     * @param quartz
     * @throws Exception
     */
    public void addJob(Quartz quartz) throws Exception {
        Class cls = Class.forName(quartz.getJobClassName()) ;
        cls.newInstance();
        //构建job信息
        JobDetail job = JobBuilder.newJob(cls).withIdentity(quartz.getJobName(),quartz.getJobGroup()).withDescription(quartz.getDescription()).build();
        // 触发时间点
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(quartz.getCronExpression());
        Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger_"+quartz.getJobName(), quartz.getJobGroup()).withDescription(quartz.getDescription()).startNow().withSchedule(cronScheduleBuilder).build();
        //交由Scheduler安排触发
        scheduler.scheduleJob(job, trigger);
    }

    /**
     * 编辑
     * @param quartz
     * @return
     * @throws SchedulerException
     */
    @PostMapping("/edit")
    public Result edit(Quartz quartz) throws SchedulerException {
        LOGGER.info("任务列表");
        //如果是修改  展示旧的 任务
        if(quartz.getOldJobGroup()!=null){
            TriggerKey triggerKey = TriggerKey.triggerKey(quartz.getOldJobName(), quartz.getOldJobGroup());
            // 停止触发器
            scheduler.pauseTrigger(triggerKey);
            // 移除触发器
            scheduler.unscheduleJob(triggerKey);
            JobKey key = new JobKey(quartz.getOldJobName(),quartz.getOldJobGroup());
            scheduler.deleteJob(key);
            System.out.println("移除任务:"+JSON.toJSONString(key));
        }
        quartz.setJobStatus(JobStatus.STOP.getStatus());
        quartz.setModifyTime(LocalDateTime.now());
        quartzService.updateById(quartz);
        return Result.ok();
    }

    @GetMapping(value="/query")
    @ResponseBody
    public Result query(Long id) throws Exception {
        return quartzService.getDetail(id);
    }

    @PostMapping("/trigger")
    public  Result trigger(Quartz quartz, HttpServletResponse response) {
        LOGGER.info("触发任务");
        try {
            JobKey key = new JobKey(quartz.getJobName(),quartz.getJobGroup());
            scheduler.triggerJob(key);
        } catch (SchedulerException e) {
            e.printStackTrace();
            return Result.error();
        }
        return Result.ok();
    }

    @PostMapping("/pause")
    public  Result pause(Long id) {
        LOGGER.info("停止任务");
        try {
            Quartz quartz = quartzService.getById(id);
            if(quartz == null){
                return Result.error("操作异常");
            }
            if(JobStatus.PAUSED.getStatus().equals(quartz.getJobStatus())){
                //停止则恢复
                JobKey key = new JobKey(quartz.getJobName(),quartz.getJobGroup());
                scheduler.resumeJob(key);
                quartz.setJobStatus(JobStatus.RUN.getStatus());
            }else if(JobStatus.RUN.getStatus().equals(quartz.getJobStatus())){
                JobKey key = new JobKey(quartz.getJobName(),quartz.getJobGroup());
                scheduler.pauseJob(key);
                quartz.setJobStatus(JobStatus.PAUSED.getStatus());
            }else{
                return Result.error(ApiResultEnum.TASK_NOT_RUNING);
            }
            quartzService.updateById(quartz);
        } catch (SchedulerException e) {
            e.printStackTrace();
            return Result.error();
        }
        return Result.ok();
    }


    @PostMapping("/remove")
    public Result remove(Long id) {
        LOGGER.info("移除任务");
        try {
            Quartz quartz = quartzService.getById(id);
            if(quartz == null){
                return Result.error("操作异常");
            }
            TriggerKey triggerKey = TriggerKey.triggerKey(quartz.getJobName(), quartz.getJobGroup());
            // 停止触发器
            scheduler.pauseTrigger(triggerKey);
            // 移除触发器
            scheduler.unscheduleJob(triggerKey);
            // 删除任务
            scheduler.deleteJob(JobKey.jobKey(quartz.getJobName(), quartz.getJobGroup()));
            System.out.println("removeJob:"+JobKey.jobKey(quartz.getJobName()));
            quartzService.removeById(id);
        } catch (Exception e) {
            e.printStackTrace();
            return Result.error();
        }
        return Result.ok();
    }
}

5、JobListener监听器

JobListener


import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;

public class MyJobListener implements JobListener {

    @Override
    public String getName() {
        String name = this.getClass().getSimpleName();
        System.out.println("监听器的名称是:" +name);
        return name;
    }

    @Override
    public void jobToBeExecuted(JobExecutionContext context) {
        String name = context.getJobDetail().getKey().getName();
        System.out.println("Job的名称是:" +name + "          Scheduler在JobDetail将要被执行时调用的方法");
    }

    @Override
    public void jobExecutionVetoed(JobExecutionContext context) {
        String name = context.getJobDetail().getKey().getName();
        System.out.println("Job的名称是:" +name + "          Scheduler在JobDetail即将被执行,但又被TriggerListener否决时会调用该方法");
    }

    @Override
    public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
        String name = context.getJobDetail().getKey().getName();
        System.out.println("Job的名称是:" +name + "          Scheduler在JobDetail被执行之后调用这个方法");
    }

}

SchedulerListener


import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.SchedulerException;
import org.quartz.SchedulerListener;
import org.quartz.Trigger;
import org.quartz.TriggerKey;

public class MySchedulerListener implements SchedulerListener {

    @Override
    public void jobScheduled(Trigger trigger) {
        String name = trigger.getKey().getName();
        // 用于部署JobDetail时调用
        System.out.println(name +" 完成部署");
    }

    @Override
    public void jobUnscheduled(TriggerKey triggerKey) {
        String name = triggerKey.getName();
        // 用于卸载JobDetail时调用
        System.out.println(name +" 完成卸载");
    }

    @Override
    public void triggerFinalized(Trigger trigger) {
        String name = trigger.getKey().getName();
        // 当一个Trigger来到了再也不会触发的状态时调用这个方法。除非这个Job已设置成了持久性,否则它就会从Scheduler中移除。
        System.out.println(name +" 触发器被移除");
    }

    @Override
    public void triggerPaused(TriggerKey triggerKey) {
        String name = triggerKey.getName();
        // Scheduler调用这个方法是发生在一个Trigger或Trigger组被暂停时。假如是Trigger组的话,triggerName参数将为null。
        System.out.println(name +" 正在被暂停");
    }

    @Override
    public void triggersPaused(String triggerGroup) {
        // Scheduler调用这个方法是发生在一个Trigger或Trigger组被暂停时。假如是Trigger组的话,triggerName参数将为null。
        System.out.println("触发器组" +triggerGroup +" 正在被暂停");
    }

    @Override
    public void triggerResumed(TriggerKey triggerKey) {
        // Scheduler调用这个方法是发生在一个Trigger或Trigger组从暂停中恢复时。假如是Trigger组的话,triggerName参数将为null。参数将为null。
        String name = triggerKey.getName();
        System.out.println(name +" 正在从暂停中恢复");
    }

    @Override
    public void triggersResumed(String triggerGroup) {
        // Scheduler调用这个方法是发生在一个Trigger或Trigger组从暂停中恢复时。假如是Trigger组的话,triggerName参数将为null。参数将为null。
        System.out.println("触发器组" +triggerGroup +" 正在从暂停中恢复");
    }

    @Override
    public void jobAdded(JobDetail jobDetail) {
        // 
        System.out.println(jobDetail.getKey() +" 添加工作任务");
    }

    @Override
    public void jobDeleted(JobKey jobKey) {
        // 
        System.out.println(jobKey +" 删除工作任务");
    }

    @Override
    public void jobPaused(JobKey jobKey) {
        // 
        System.out.println(jobKey +" 工作任务正在被暂停");
    }

    @Override
    public void jobsPaused(String jobGroup) {
        // 
        System.out.println("工作组" +jobGroup +" 正在被暂停");
    }

    @Override
    public void jobResumed(JobKey jobKey) {
        // 
        System.out.println(jobKey +" 正在从暂停中恢复");
    }

    @Override
    public void jobsResumed(String jobGroup) {
        // 
        System.out.println("工作组" +jobGroup +" 正在从暂停中恢复");
    }

    @Override
    public void schedulerError(String msg, SchedulerException cause) {
        // 在Scheduler的正常运行期间产生一个严重错误时调用这个方法。
        System.out.println("产生严重错误的时候调用" +msg +"    " +cause.getUnderlyingException());
    }

    @Override
    public void schedulerInStandbyMode() {
        // 当Scheduler处于StandBy模式时,调用该方法。
        System.out.println("调度器被挂起模式的时候调用");
    }

    @Override
    public void schedulerStarted() {
        // 当Scheduler开启时,调用该方法
        System.out.println("调度器开启的时候调用");
    }

    @Override
    public void schedulerStarting() {
        // 
        System.out.println("调度器正在开启的时候调用");
    }

    @Override
    public void schedulerShutdown() {
        // 
        System.out.println("调度器关闭的时候调用");
    }

    @Override
    public void schedulerShuttingdown() {
        // 
        System.out.println("调度器正在关闭的时候调用");
    }

    @Override
    public void schedulingDataCleared() {
        // 当Scheduler中的数据被清除时,调用该方法
        System.out.println("调度器数据被清除的时候调用");
    }

}

TriggerListener


import org.quartz.JobExecutionContext;
import org.quartz.Trigger;
import org.quartz.Trigger.CompletedExecutionInstruction;
import org.quartz.TriggerListener;

public class MyTriggerListener implements TriggerListener {
    
//    private String name;
//    // 构造方法,自定义传递触发器的名称,默认是类的名称
//    public MyTriggerListener(String name) {
//        super();
//        this.name = name;
//    }
//    @Override
//    public String getName() {
//        return this.name;  // 不返还会抛出一个名称为空的异常
//    }

    @Override
    public String getName() {
        String name = this.getClass().getSimpleName();
        System.out.println("触发器的名称:" +name);
        return name;  // 不返还会抛出一个名称为空的异常
    }

    @Override
    public void triggerFired(Trigger trigger, JobExecutionContext context) {
        String name = this.getClass().getSimpleName();
        System.out.println(name +"被触发");
    }

    @Override
    public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
        String name = this.getClass().getSimpleName();
        // TriggerListener给了一个选择去否决Job的执行。假如这个方法返回true,这个Job将不会为此次Trigger触发而得到执行。
        System.out.println(name +" 没有被触发");
        return false;  // true:表示不会执行Job的方法
    }

    @Override
    public void triggerMisfired(Trigger trigger) {
        String name = this.getClass().getSimpleName();
        // Scheduler调用这个方法是在Trigger错过触发时
        System.out.println(name +" 错过触发");
    }

    @Override
    public void triggerComplete(Trigger trigger, JobExecutionContext context,
            CompletedExecutionInstruction triggerInstructionCode) {
        String name = this.getClass().getSimpleName();
        // Trigger被触发并且完成了Job的执行时,Scheduler调用这个方法。
        System.out.println(name +" 完成之后触发");
    }

}

 public static void main(String[] args) throws Exception {
        // 1、调度器(Scheduler),从工厂中获取调度的实例(默认:实例化new StdSchedulerFactory();)
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

        // 2、任务实例(JobDetail)定义一个任务调度实例,将该实例与HelloJobSimpleTrigger绑定,任务类需要实现Job接口
        JobDetail jobDetail = JobBuilder.newJob(HelloJobListener.class) // 加载任务类,与HelloJob完成绑定,要求HelloJob实现Job接口
                .withIdentity("job1", "group1") // 参数1:任务的名称(唯一实例);参数2:任务组的名称
                .usingJobData("message", "打印日志") // 传递参数,名称message
                .usingJobData("count", 0)
                .build();

        System.out.println("name:" +jobDetail.getKey().getName());
        System.out.println("group:" +jobDetail.getKey().getGroup()); // 如果没有指定组名:DEFAULT
        System.out.println("jobClass:" +jobDetail.getJobClass().getName());
        
        // 3、触发器(Trigger)定义触发器,马上执行,然后每5秒重复执行一次
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1") // 参数1:触发器的名称(唯一实例);参数2:触发器组的名称
                .startNow()
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatSecondlyForever(5).withRepeatCount(2))  // 每5秒执行一次,连续执行3次后停止,默认是0
                .build();
        // 4、让调度器关联任务和触发器,保证按照触发器定义的调整执行任务
        scheduler.scheduleJob(jobDetail, trigger);

        // 创建调度器的监听
        scheduler.getListenerManager().addSchedulerListener(new MySchedulerListener());

        // 移除对应的调度器的监听
        // scheduler.getListenerManager().removeSchedulerListener(new MySchedulerListener());
        
        // 创建并注册一个全局的Job Listener
        //scheduler.getListenerManager().addJobListener(new MyJobListener(), EverythingMatcher.allJobs());
        
        // 创建并注册一个局部的Job Listener,表示指定的任务Job
        //scheduler.getListenerManager().addJobListener(new MyJobListener(), KeyMatcher.keyEquals(JobKey.jobKey("job1", "group1")));

        
        // 创建并注册一个全局的Trigger Listener
        // scheduler.getListenerManager().addTriggerListener(new MyTriggerListener(), EverythingMatcher.allTriggers());
        
        // 创建并注册一个局部的Trigger Listener
        //scheduler.getListenerManager().addTriggerListener(new MyTriggerListener(), KeyMatcher.keyEquals(TriggerKey.triggerKey("trigger1", "group1")));



        // 5、启动
        scheduler.start();

        /**
         * shutdown(true):表示等待所有正在执行的Job执行完毕之后,再关闭Scheduler
         * shutdown(false): 表示直接关闭Scheduler
         */
        //scheduler.shutdown(false);
        
        // 挂起
        //scheduler.standby();
        
        // 线程延迟7秒后关闭
        Thread.sleep(7000L);

        // 关闭
        scheduler.shutdown();
    }

参考代码:动态定时任务


import lombok.extern.slf4j.Slf4j;
import name.ealen.domain.entity.TaskDefine;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Service;

import java.util.Map;

/**
 * Created by EalenXie on 2019/7/10 13:49.
 * 核心其实就是Scheduler的功能 , 这里只是非常简单的示例说明其功能
 * 如需根据自身业务进行扩展 请参考 {@link org.quartz.Scheduler}
 */
@Slf4j
@Service
public class QuartzJobService {

    //Quartz定时任务核心的功能实现类
    private Scheduler scheduler;

    public QuartzJobService(@Autowired SchedulerFactoryBean schedulerFactoryBean) {
        scheduler = schedulerFactoryBean.getScheduler();
    }

    /**
     * 创建和启动 定时任务
     * {@link org.quartz.Scheduler#scheduleJob(JobDetail, Trigger)}
     *
     * @param define 定时任务
     */
    public void scheduleJob(TaskDefine define) throws SchedulerException {
        //1.定时任务 的 名字和组名
        JobKey jobKey = define.getJobKey();
        //2.定时任务 的 元数据
        JobDataMap jobDataMap = getJobDataMap(define.getJobDataMap());
        //3.定时任务 的 描述
        String description = define.getDescription();
        //4.定时任务 的 逻辑实现类
        Class<? extends Job> jobClass = define.getJobClass();
        //5.定时任务 的 cron表达式
        String cron = define.getCronExpression();
        JobDetail jobDetail = getJobDetail(jobKey, description, jobDataMap, jobClass);
        Trigger trigger = getTrigger(jobKey, description, jobDataMap, cron);
        scheduler.scheduleJob(jobDetail, trigger);
    }


    /**
     * 暂停Job
     * {@link org.quartz.Scheduler#pauseJob(JobKey)}
     */
    public void pauseJob(JobKey jobKey) throws SchedulerException {
        scheduler.pauseJob(jobKey);
    }

    /**
     * 恢复Job
     * {@link org.quartz.Scheduler#resumeJob(JobKey)}
     */
    public void resumeJob(JobKey jobKey) throws SchedulerException {
        scheduler.resumeJob(jobKey);
    }

    /**
     * 删除Job
     * {@link org.quartz.Scheduler#deleteJob(JobKey)}
     */
    public void deleteJob(JobKey jobKey) throws SchedulerException {
        scheduler.deleteJob(jobKey);
    }


    /**
     * 修改Job 的cron表达式
     */
    public boolean modifyJobCron(TaskDefine define) {
        String cronExpression = define.getCronExpression();
        //1.如果cron表达式的格式不正确,则返回修改失败
        if (!CronExpression.isValidExpression(cronExpression)) return false;
        JobKey jobKey = define.getJobKey();
        TriggerKey triggerKey = new TriggerKey(jobKey.getName(), jobKey.getGroup());
        try {
            CronTrigger cronTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
            JobDataMap jobDataMap = getJobDataMap(define.getJobDataMap());
            //2.如果cron发生变化了,则按新cron触发 进行重新启动定时任务
            if (!cronTrigger.getCronExpression().equalsIgnoreCase(cronExpression)) {
                CronTrigger trigger = TriggerBuilder.newTrigger()
                        .withIdentity(triggerKey)
                        .withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
                        .usingJobData(jobDataMap)
                        .build();
                scheduler.rescheduleJob(triggerKey, trigger);
            }
        } catch (SchedulerException e) {
            log.error("printStackTrace", e);
            return false;
        }
        return true;
    }


    /**
     * 获取定时任务的定义
     * JobDetail是任务的定义,Job是任务的执行逻辑
     *
     * @param jobKey      定时任务的名称 组名
     * @param description 定时任务的 描述
     * @param jobDataMap  定时任务的 元数据
     * @param jobClass    {@link org.quartz.Job} 定时任务的 真正执行逻辑定义类
     */
    public JobDetail getJobDetail(JobKey jobKey, String description, JobDataMap jobDataMap, Class<? extends Job> jobClass) {
        return JobBuilder.newJob(jobClass)
                .withIdentity(jobKey)
                .withDescription(description)
                .setJobData(jobDataMap)
                .usingJobData(jobDataMap)
                .requestRecovery()
                .storeDurably()
                .build();
    }


    /**
     * 获取Trigger (Job的触发器,执行规则)
     *
     * @param jobKey         定时任务的名称 组名
     * @param description    定时任务的 描述
     * @param jobDataMap     定时任务的 元数据
     * @param cronExpression 定时任务的 执行cron表达式
     */
    public Trigger getTrigger(JobKey jobKey, String description, JobDataMap jobDataMap, String cronExpression) {
        return TriggerBuilder.newTrigger()
                .withIdentity(jobKey.getName(), jobKey.getGroup())
                .withDescription(description)
                .withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
                .usingJobData(jobDataMap)
                .build();
    }


    public JobDataMap getJobDataMap(Map<?, ?> map) {
        return map == null ? new JobDataMap() : new JobDataMap(map);
    }


}

Q.E.D.