SpringBoot2.x学习-任务调度-Quartz集成

一、Quartz介绍

1.1 介绍说明

Quartz 是 OpenSymphony 开源组织在任务调度领域的一个开源项目,自2001年发布以来,已经被众多项目作为任务调度的解决方案。
Quartz是一个任务调度框架,完全基于 Java 实现,该项目于 2009 年被 Terracotta 收购目前是 Terracotta 旗下的一个项目

1.2 地址和类库说明

官方地址:http://www.quartz-scheduler.org/
从官方地址下载quartz-2.3.0-distribution.tar.gz包(当时最新的稳定版本),然后解压开如下:
在这里插入图片描述
目录说明

  1. examples目录放的是官方提供的15个Demo实例
  2. javadoc目录是Quartz官方的API文档,HTML格式的
  3. lib目录放的是Quartz类库jar包
  4. src目录放的是Quartz类库源码和sql文件

在quartz-2.3.0-SNAPSHOT\src\org\quartz\impl\jdbcjobstore目录下面有官方提供的各种数据库表sql,比如:tables_mysql_innodb.sql,在集群配置的时候需要用到。如果是quartz-2.3.以下版本,则在quartz-2.2.3\docs\dbTables目录下存放各种数据库的表sql

1.3 Quartz基础结构

Quartz有调度器、任务和触发器3个核心概念,以下分别介绍说明

  • Job:是一个接口,只有一个方法void execute(JobExecutionContext var1) throws JobExecutionException,开发者通过实现这个接口来定义需要执行的任务,JobExecutionContext 类提供了调度上下文的各种信息,job运行时的信息保存在JobDataMap中

  • JobDetail: Quartz在每次执行job时,都重新创建一个Job实例,所以它不是直接接收一个Job实例而是接收一个Job实现类,以便运行时通过反射调用机制实例化Job。因此需要通过一个类来描述Job的实现类及其他相关的静态信息,如Job名称,描述等信息,JobDetail接口相当于将Job接口包装了一下。

  • Trigger:触发器描述触发Job执行的时间触发规则。有CronTrigger、CalendarIntervalTrigger、DailyTimeIntervalTrigger、SimpleTrigger等4个子接口,其中CronTrigger用的比较多。

  • Scheduler:任务调度器,是实际执行任务调度的控制器。Quartz通过Scheduler调度器来注册、暂停、删除Trigger和JobDetail。Trigger和JobDetail在Scheduler容器中拥有各自的组及名称,组和名称是Scheduler查找定位容器中某一个对象的依据,Trigger的组及名称的组和必须唯一,JobDetail的组及名称的组和也必须唯一(可以和Trigger的组及名称相同)。Scheduler将Trigger绑定到某一JobDetail中,这样当Trigger被触发时,对应的Job就被执行。一个Job可以对应多个Trigger,但一个Trigger只能对应一个Job。

  • SchedulerFactory:任务调度器工厂,用于创建Scheduler实例,Spring还提供了SchedulerFactoryBean类,封装了SchedulerFactory。SchedulerFactory有两个实现:DirectSchedulerFactory和 StdSchdulerFactory。前者可以用来在代码里定制你自己的Schduler参数。后者是直接读取classpath下的quartz.properties(不存在就都使用默认值)配置来实例化Schduler。通常来讲,我们使用StdSchdulerFactory也就足够了。

  • ThreadPool 线程池,Quartz有自己的线程池实现。所有任务的都会由线程池执行

    JobDetail和Trigger都有name属性和group属性。name属性是它们在这个Sheduler里面的唯一标识。group属性是一个组织单元,sheduler会提供一些对整组操作的API,比如 scheduler.resumeJobs(GroupMatcher matcher)),就是恢复某一个分组里的所有JobDetails

    Quartz有完善的事件和监听体系,大部分组件都拥有事件,例如在任务执行前的事件、任务执行后事件、触发器触发前事件、触发器触发后事件、调度器开始事件、调度器关闭事件等等。

    一个Scheduler可以拥有多个Trigger和多个JobDetail,它们可以分到不同的组中。在注册Trigger和JobDetail时,如果不显示指定所属组,那Scheduler将放入默认组中,默认组的组名为Scheduler.DEFAULT_GROUP。所属组名和名称组成了对象的全名,同一类对象的全名不能相同。

    当Scheduler比较繁忙或者资源不足(线程池)的时候,可能存在同一个时刻,有多个Trigger被触发了,这时候可以指定Trigger的优先级,优先级高的先执行。需要注意的是设置优先级只有在同一时刻执行的Trigger之间才会起作用,优先级的值默认是 Thread.NORM_PRIORITY即5。

    Misfire(错失触发):当Scheduler资源不足的时候或者机器崩溃重启时,有可能导致某一些Trigger在应该触发的时间点没有被触发,也就是Miss Fire了。这个时候Trigger需要一个策略来处理这种情况。也就是Trigger到了该触发执行的时候,上一个执行还未完成,并且线程池中没有空闲线程可以使用(或有空闲线程可以使用但job设置为@DisallowConcurrentExecution)且过期时间已经超过misfireThreshold就认为是misfire了,错失触发了

二、Spring Boot集成Quartz

Jdk1.8
Spring Boot 2.3.8.RELEASE
Mybatis-plus 3.4.2

2.1添加quartz依赖

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

2.2 Cron Triggers简单例子

SimpleJob.java

import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobKey;

import java.util.Date;

/**
 * @author 
 * @Description 简单任务
 * @Date 2021/4/10 23:00
 * @Version 1.0
 */
public class SimpleJob implements Job {

    /**
     * 日志工具
     */
    Log logger = LogFactory.get();

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        JobKey jobKey = jobExecutionContext.getJobDetail().getKey();
        logger.info("简单任务(SimpleJob) says: " + jobKey + " executing at " + new Date());
    }
}

QuartzTest.java

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

/**
 * @author David 
 * @Description TODO
 * @Date 2021/4/10 22:59
 * @Version 1.0
 */
public class QuartzTest {
    public static void main(String[] args) {

        try {
            //Scheduler 调度器实例
            SchedulerFactory schedulerFactory = new StdSchedulerFactory();
            Scheduler scheduler = schedulerFactory.getScheduler();

            // 创建jobDetail实例,绑定Job实现类
            JobDetail job = JobBuilder.newJob(SimpleJob.class)
                    .withIdentity("job1", "group1").build();
            //定义调度触发规则,这里使用cornTrigger规则,每10S触发一次
            CronTrigger trigger = TriggerBuilder
                    .newTrigger().withIdentity("trigger1", "group1")
                    .withSchedule(CronScheduleBuilder.cronSchedule("0/10 * * * * ?"))
                     //指定trigger优先级
                    .withPriority(Thread.MIN_PRIORITY)
                    .build();

            // 把作业和触发器注册到任务调度中
            scheduler.scheduleJob(job, trigger);
            
            //第二个job
            job = JobBuilder.newJob(SimpleJob.class)
                    .withIdentity("job2", "group1").build();
            //第二个触发器,每16秒跑一次
            trigger = TriggerBuilder
                    .newTrigger().withIdentity("trigger2", "group1")
                    .withSchedule(CronScheduleBuilder.cronSchedule("0/16 * * * * ?"))
                    .build();
            scheduler.scheduleJob(job, trigger);

            //启动调度器
            scheduler.start();
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }
}

执行QuartzTest 类就可以看到打印的结果

三、自定义动态配置任务

假设Web项目需要能够实时的启动或暂停某一个任务,添加的新任务要保存到数据库中,并且项目重启的时候要能够把有效状态的定时器自动启动执行,要记录每个任务执行的时间。

3.1创建表存储任务信息

这里就先不用Quartz官方提供的表结构来存储任务,自己定一个MySql表结构如下:

CREATE TABLE `t_system_shedule` (
	`ID` VARCHAR(32) NOT NULL COMMENT '定时ID' COLLATE 'utf8_general_ci',
	`SHEDULE_NAME` VARCHAR(128) NULL DEFAULT NULL COMMENT '定时器名称' COLLATE 'utf8_general_ci',
	`SHEDULE_CODE` VARCHAR(32) NULL DEFAULT NULL COMMENT '定时器编码' COLLATE 'utf8_general_ci',
	`SHEDULE_CRON` VARCHAR(256) NULL DEFAULT NULL COMMENT '定时器CRON表达式' COLLATE 'utf8_general_ci',
	`SHEDULE_CLASSNAME` VARCHAR(256) NULL DEFAULT NULL COMMENT '定时器处理类' COLLATE 'utf8_general_ci',
	`SHEDULE_STATUS` CHAR(1) NULL DEFAULT '1' COMMENT '定时器状态(1:启用:-1:停用)' COLLATE 'utf8_general_ci',
	`SHEDULE_BINDIP` CHAR(16) NULL DEFAULT NULL COMMENT '绑定调度的IP地址' COLLATE 'utf8_general_ci',
	`SHEDULE_GROUP` VARCHAR(128) NULL DEFAULT 'DEFAULT' COMMENT '定时器分组' COLLATE 'utf8_general_ci',
	`SHEDULE_DESCRIPTION` VARCHAR(512) NULL DEFAULT NULL COMMENT '定时器描述' COLLATE 'utf8_general_ci',
	`CREATE_TIME` DATETIME NULL DEFAULT NULL COMMENT '创建时间',
	`UPDATE_TIME` DATETIME NULL DEFAULT NULL COMMENT '更新时间',
	PRIMARY KEY (`ID`) USING BTREE
)
COMMENT='定时任务表'
COLLATE='utf8_general_ci'
ENGINE=InnoDB
;

3.2 Quartz配置

applicatiion.yml

spring:
  profiles:
    active: dev
  application:
    name: fire
  quartz:
    # 将任务等持久化到数据库
    #job-store-type: jdbc
    # 程序结束时会等待quartz相关的内容结束
    wait-for-jobs-to-complete-on-shutdown: true
    properties:
      org:
        quartz:
          threadPool:
            class: org.quartz.simpl.SimpleThreadPool
            #线程数
            threadCount: 4
            # 线程优先级
            threadPriority: 5
            threadsInheritContextClassLoaderOfInitializingThread: true
#.....还有Druid数据源配置,就不展示了

QuartzConfig.java

import com.soft.fire.listener.QuartzJobListener;
import org.quartz.Scheduler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;

/**
 * @author David 
 * @Description quartz定时任务配置
 * @Date 2021/4/8 15:20
 * @Version 1.0
 */
@Configuration
public class QuartzConfig {

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() {
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        //指定JobFactory,用于自定义Job中注入SpringBean
        schedulerFactoryBean.setJobFactory(new SpringBeanJobFactory());
        schedulerFactoryBean.setOverwriteExistingJobs(true);
        schedulerFactoryBean.setApplicationContextSchedulerContextKey("applicationContextKey");
        //设置全局任务监听器
        schedulerFactoryBean.setGlobalJobListeners(new QuartzJobListener());
        return schedulerFactoryBean;
    }

    /**
     * 创建scheduler
     *
     * @return
     */
    @Bean
    public Scheduler quartzScheduler() {
        Scheduler scheduler = schedulerFactoryBean().getScheduler();
        //添加TriggerListener监听器
        //scheduler.getListenerManager().addTriggerListener();
        //添加 SchedulerListener监听器,可以监听JobDetail的部署、卸载、暂停和恢复
        //scheduler.getListenerManager().addSchedulerListener();

        //添加局部任务监听器
       /* scheduler.getListenerManager()
                .addJobListener(new QuartzJobListener(),
                        KeyMatcher.keyEquals(JobKey.jobKey("jobname","groupname")));*/
        return scheduler;
    }
}

QuartzJobListener.java,任务监听可以用于记录日志

import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;

/**
 * @author David 
 * @Description 定时器执行监听器,用于跟踪定时器执行过程状态变化
 * @Date 2021/4/9 9:29
 * @Version 1.0
 */
public class QuartzJobListener implements JobListener {

    /**
     * 日志操作对象
     */
    private Log logger = LogFactory.get();

    /**
     * 返回一个字符串用以说明 JobListener 的名称
     * 对于注册为全局的监听器,getName() 主要用于记录日志
     * 对于由特定 Job 引用的 JobListener,注册在 JobDetail 上的监听器名称必须匹配从监听器上 getName() 方法的返回值。
     *
     * @return
     */
    @Override
    public String getName() {
        return this.getClass().getSimpleName();
    }

    /**
     * Scheduler 在 JobDetail 将要被执行时调用这个方法。
     */
    @Override
    public void jobToBeExecuted(JobExecutionContext jobExecutionContext) {
        String jobName = jobExecutionContext.getJobDetail().getKey().getName();
        logger.info("--------------jobToBeExecuted定时器开始执行之前调用的监听方法---jobName={}------", jobName);
    }

    /**
     * Scheduler 在 JobDetail 即将被执行,但又被 TriggerListener 否决了时调用这个方法。
     *
     * @param jobExecutionContext
     */
    @Override
    public void jobExecutionVetoed(JobExecutionContext jobExecutionContext) {
        String jobName = jobExecutionContext.getJobDetail().getKey().getName();
        logger.info("------------jobExecutionVetoed定时器开始执行过程调用的监听方法---jobName={}------", jobName);
    }

    /**
     * Scheduler 在 JobDetail 被执行之后调用这个方法。
     */
    @Override
    public void jobWasExecuted(JobExecutionContext jobExecutionContext, JobExecutionException e) {
        String jobName = jobExecutionContext.getJobDetail().getKey().getName();
        logger.info("--------------jobWasExecuted定时器执行完成之后调用的监听方法---jobName={}------", jobName);
    }
}

3.3 注册、卸载、暂停、恢复任务操作

SheduleServiceImpl.java

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.soft.fire.constant.enums.SheduleStatusEnum;
import com.soft.fire.platform.system.mapper.SheduleMapper;
import com.soft.fire.platform.system.model.Shedule;
import com.soft.fire.platform.system.model.SheduleDTO;
import com.soft.fire.platform.system.service.SheduleService;
import org.quartz.*;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

/**
 * <p>
 * 定时任务表 服务实现类
 * </p>
 *
 * @author David
 * @since 2021-04-08
 */
@Service
public class SheduleServiceImpl extends ServiceImpl<SheduleMapper, Shedule>
        implements SheduleService {

    /**
     * 注入mapper依赖
     */
    @Resource
    private SheduleMapper sheduleMapper;
    /**
     * 注入scheduler
     */
    @Resource
    private Scheduler quartzScheduler;

    /**
     * 初始化定时任务
     */
    @Override
    public void initShedule() throws Exception {
        //从自定义表里面查询所有启用状态的任务信息 注册到Scheduler中
        List<Shedule> sheduleList = this.list(new QueryWrapper<Shedule>()
                .eq("shedule_status", SheduleStatusEnum.QUARTZ_ENABLE.getValue()));
        for (Shedule shedule : sheduleList) {
            addScheduleJob(shedule);
        }
    }

    /**
     * 添加定时任务
     *
     * @param shedule
     */
    @Override
    public void addScheduleJob(Shedule shedule) throws Exception {
        // 创建jobDetail实例,绑定Job实现类,然后指明job的名称,所在组的名称
        JobDetail jobDetail = JobBuilder.newJob((Class<? extends Job>) Class.forName(shedule.getSheduleClassname()))
                .withIdentity(shedule.getSheduleCode(), shedule.getSheduleGroup()).build();
        // 定义调度触发规则,这里使用cornTrigger规则
        CronTrigger cronTrigger = TriggerBuilder.newTrigger()
                .withIdentity(shedule.getSheduleCode(), shedule.getSheduleGroup())
                .startAt(DateBuilder.futureDate(1, DateBuilder.IntervalUnit.SECOND))
                .withSchedule(CronScheduleBuilder.cronSchedule(shedule.getSheduleCron()))
                .startNow()
                .build();
        // 把作业和触发器注册到任务调度中
        quartzScheduler.scheduleJob(jobDetail, cronTrigger);

        //启动
        if (!quartzScheduler.isStarted()) {
            quartzScheduler.start();
        }
    }

    /**
     * 新增定时任务
     *
     * @param sheduleDTO
     */
    @Override
    public void saveShedule(SheduleDTO sheduleDTO) throws Exception {
        Shedule shedule = new Shedule();
        BeanUtil.copyProperties(sheduleDTO, shedule);
        this.addScheduleJob(shedule);
        // 保存任务信息到自定义表
        this.save(shedule);
    }

    /**
     * 修改定时任务
     *
     * @param sheduleDTO
     */
    @Override
    public void modifyShedule(SheduleDTO sheduleDTO) throws Exception {
        //修改的是启用状态的并且Scheduler已存在的任务
        if (SheduleStatusEnum.QUARTZ_ENABLE.getValue().equals(sheduleDTO.getSheduleStatus())
                && quartzScheduler.checkExists(JobKey.jobKey(sheduleDTO.getSheduleCode(),
                sheduleDTO.getSheduleGroup()))) {
            TriggerKey triggerKey = TriggerKey.triggerKey(sheduleDTO.getSheduleCode(), sheduleDTO.getSheduleGroup());
            CronTrigger trigger = (CronTrigger) quartzScheduler.getTrigger(triggerKey);
            CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(sheduleDTO.getSheduleCron());
            trigger = trigger.getTriggerBuilder()
                    .withIdentity(triggerKey)
                    .withSchedule(cronScheduleBuilder).build();
            quartzScheduler.rescheduleJob(triggerKey, trigger);
        }

        Shedule shedule = new Shedule();
        BeanUtil.copyProperties(sheduleDTO, shedule);
        //修改数据库任务信息
        this.updateById(shedule);

    }

    /**
     * 暂停定时器
     *
     * @param shedule
     */
    @Override
    public void pauseShedule(Shedule shedule) throws Exception {
        if (quartzScheduler.checkExists(JobKey.jobKey(shedule.getSheduleCode(),
                shedule.getSheduleGroup()))) {
            quartzScheduler.pauseJob(new JobKey(shedule.getSheduleCode(),
                    shedule.getSheduleGroup()));
        }
        UpdateWrapper<Shedule> updateWrapper = new UpdateWrapper<>();
        updateWrapper.set("shedule_status", SheduleStatusEnum.QUARTZ_DISABLE.getValue());
        updateWrapper.eq("id", shedule.getId());
        //修改数据库状态
        this.update(updateWrapper);
    }

    /**
     * 恢复定时器
     *
     * @param shedule
     */
    @Override
    public void resumeShedule(Shedule shedule) throws Exception {
        //先判断Scheduler中是否存在
        if (quartzScheduler.checkExists(JobKey.jobKey(shedule.getSheduleCode(),
                shedule.getSheduleGroup()))) {
            quartzScheduler.resumeJob(new JobKey(shedule.getSheduleCode(), shedule.getSheduleGroup()));
        } else {
            //不存在就加入到Scheduler调度中
            this.addScheduleJob(shedule);
        }
        UpdateWrapper<Shedule> updateWrapper = new UpdateWrapper<>();
        updateWrapper.set("shedule_status", SheduleStatusEnum.QUARTZ_ENABLE.getValue());
        updateWrapper.eq("id", shedule.getId());
        //修改数据库状态
        this.update(updateWrapper);
    }

    /**
     * 删除定时器
     *
     * @param shedule
     */
    @Override
    public void deleteShedule(Shedule shedule) throws Exception {
        if (quartzScheduler.checkExists(JobKey.jobKey(shedule.getSheduleCode(),
                shedule.getSheduleGroup()))) {
            quartzScheduler.deleteJob(new JobKey(shedule.getSheduleCode(), shedule.getSheduleGroup()));
        }
        //删除定义表里面的任务数据
        this.removeById(shedule.getId());
    }
}

3.4 Controller和Model

SheduleController.java

import cn.hutool.core.util.StrUtil;
import com.soft.fire.platform.system.model.Shedule;
import com.soft.fire.platform.system.model.SheduleDTO;
import com.soft.fire.platform.system.service.SheduleService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.List;

/**
 * <p>
 * 定时任务表 前端控制器
 * </p>
 *
 * @author David
 * @since 2021-04-08
 */
@RestController
@RequestMapping("/system/shedule")
@Api(tags = "quartz定时任务API")
public class SheduleController {

    /**
     * 注入service
     */
    @Resource
    private SheduleService sheduleService;

    /**
     * 查询出所有的定时任务信息
     *
     * @return
     */
    @GetMapping
    @ApiOperation("查询出所有定时任务")
    public List<Shedule> all() {
        return sheduleService.list();
    }

    /**
     * 新增定时任务
     *
     * @param sheduleDTO
     * @return
     */
    @PostMapping
    @ApiOperation(value = "新增或修改定时任务", notes = "如果是修改操作需要ID属性")
    public String shedule(@RequestBody SheduleDTO sheduleDTO) throws Exception {
        //TODO  判断code是否重复
        if (StrUtil.isBlank(sheduleDTO.getId())) {
            sheduleService.saveShedule(sheduleDTO);
        } else {
            sheduleService.modifyShedule(sheduleDTO);
        }
        return "success";
    }

    /**
     * 暂停定时器
     *
     * @param jobId
     * @return
     */
    @PostMapping("/pause")
    @ApiOperation("暂停定时器")
    public String pauseShedule(@RequestParam("jobId") String jobId) throws Exception {
        Shedule shedule = sheduleService.getById(jobId);
        sheduleService.pauseShedule(shedule);
        return "success";
    }

    /**
     * 恢复定时任务
     *
     * @param jobId
     * @return
     */
    @PostMapping("/resume")
    @ApiOperation("恢复定时任务")
    public String resumeShedule(@RequestParam("jobId") String jobId) throws Exception {
        Shedule shedule = sheduleService.getById(jobId);
        sheduleService.resumeShedule(shedule);
        return "success";
    }

    @DeleteMapping("{jobId}")
    @ApiOperation("删除定时任务")
    public String deleteShedule(@PathVariable("jobId") String jobId) throws Exception {
        Shedule shedule = sheduleService.getById(jobId);
        sheduleService.deleteShedule(shedule);
        return "success";
    }

}

任务实体对象Shedule.java,SheduleDTO继承自Shedule

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

import java.io.Serializable;
import java.sql.Timestamp;

/**
 * <p>
 * 定时任务表
 * </p>
 *
 * @author David
 * @since 2021-04-08
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("t_system_shedule")
@ApiModel(value = "定时任务", description = "定时任务实体对象")
public class Shedule implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 定时ID
     */
    @ApiModelProperty("定时ID")
    private String id;

    /**
     * 定时器名称
     */
    @ApiModelProperty(value = "定时器名称", required = true)
    private String sheduleName;

    /**
     * 定时器编码
     */
    @ApiModelProperty(value = "定时器编码", required = true)
    private String sheduleCode;

    /**
     * 定时器CRON表达式
     */
    @ApiModelProperty(value = "定时器CRON表达式", required = true)
    private String sheduleCron;

    /**
     * 定时器处理类
     */
    @ApiModelProperty(value = "定时器处理类", required = true)
    private String sheduleClassname;

    /**
     * 定时器状态(1:启用:-1:停用)
     */
    @ApiModelProperty(value = "定时器状态", hidden = true)
    private String sheduleStatus;

    /**
     * 定时器分组
     */
    @ApiModelProperty(value = "定时器分组", required = true)
    private String sheduleGroup;

    /**
     * 定时器描述
     */
    @ApiModelProperty(value = "定时器描述")
    private String sheduleDescription;
    /**
     * 绑定调度的IP地址,如果是应用是负载均衡部署,则这个属性可以用到
     * 如果是用Docker容器化部署的IP地址不固定就用不上
     */
    @ApiModelProperty(value = "绑定调度的IP地址")
    private String sheduleBindip;

    /**
     * 创建时间
     */
    @ApiModelProperty(value = "创建时间", hidden = true)
    @TableField(fill = FieldFill.INSERT)
    private Timestamp createTime;

    /**
     * 更新时间
     */
    @TableField(fill = FieldFill.UPDATE)
    @ApiModelProperty(value = "更新时间", hidden = true)
    private Timestamp updateTime;
}

定时任务状态枚举类,SheduleStatusEnum.java

/**
 * @author David
 * @Description quartz定时任务状态枚举
 * @Date 2021/4/8 16:18
 * @Version 1.0
 */
public enum SheduleStatusEnum {

    /**
     * 定时任务启用
     */
    QUARTZ_ENABLE("启用", "1"),
    /**
     * 定时任务禁用
     */
    QUARTZ_DISABLE("停用", "0");

    SheduleStatusEnum(String displayName, String value) {
        this.displayName = displayName;
        this.value = value;
    }

    /**
     * 显示的枚举名称
     */
    private String displayName;
    /**
     * 枚举值
     */
    private String value;

    public String getDisplayName() {
        return displayName;
    }

    public void setDisplayName(String displayName) {
        this.displayName = displayName;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }
}

3.5 SpringBoot启动监听

用于在SpringBoot项目启动的时候自动加载数据库里面的定时任务执行
QuartzJobInitListener.java

import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import com.soft.fire.platform.system.service.SheduleService;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * @author David
 * @Description quartz定时任务初始化监听器, 在springboot项目启动之后执行
 * @Date 2021/4/8 15:43
 * @Version 1.0
 */
@Component
@Order(value = 1)
public class QuartzJobInitListener implements CommandLineRunner {

    /**
     * 日志工具
     */
    private Log logger = LogFactory.get();
    /**
     * sheduleService
     */
    @Resource
    private SheduleService sheduleService;

    @Override
    public void run(String... args) throws Exception {
        logger.info("---------开始执行quartz定时任务初始化!-----------");
        try {
            sheduleService.initShedule();
        } catch (Exception e) {
            logger.error("quartz初始化失败!!", e);
        }
        logger.info("---------quartz定时任务初始化工作结束!-----------");
    }
}

3.6 新增一个任务测试下

用Swagger调试如下
在这里插入图片描述

四、集群配置(分布式任务)

quartz是支持分布式的,通过表来管理各节点之间的关系

4.1 运行tables_mysql_innodb.sql

执行 quartz-2.3.0-SNAPSHOT\src\org\quartz\impl\jdbcjobstore\tables_mysql_innodb.sql文件,创建Quartz官方提供的11张表
在这里插入图片描述
11张表说明

表名称说明描述
QRTZ_JOB_DETAILS存储每一个已配置的JOB的详细信息
QRTZ_TRIGGERS存储已配置的TRIGGER的信息
QRTZ_SIMPLE_TRIGGERS存储已配置的SIMPLE TRIGGER的信息
QRTZ_SIMPROP_TRIGGERSxx
QRTZ_CRON_TRIGGERS存储CRON TRIGGER,包括CRON表达式和时区信息
QRTZ_BLOB_TRIGGERSTRIGGER作为BLOB类型存储(用于QUARTZ用户用JDBC创建他们自己定制的TRIGGER类型,JOBSTORE并不知道如何存储实例的时候)
QRTZ_CALENDARS以BLOB类型存储QUARTZ的CALENDAR日历信息,QUARTZ可配置一个日历来指定一个时间范围
QRTZ_PAUSED_TRIGGER_GRPS存储已暂停的TRIGGER组的信息
QRTZ_FIRED_TRIGGERS存储与错失触发的TRIGGER相关的状态信息,以及相联JOB的执行信息
QRTZ_SCHEDULER_STATE存储少量的有关 SCHEDULER的状态信息,和别的 SCHEDULER 实例(假如是用于一个集群中)
QRTZ_LOCKS存储程序的非观锁的信息(假如使用了悲观锁)

4.2 分布式配置

application.yml修改为如下

spring:
  profiles:
    active: dev
  application:
    name: fire
  quartz:
    # 将任务等持久化到数据库
    job-store-type: jdbc
    # 程序结束时会等待quartz相关的内容结束
    wait-for-jobs-to-complete-on-shutdown: true
    properties:
      org:
        quartz:
          #Scheduler 调度器属性配置
          scheduler:
            # 调度标识名 集群中每一个实例都必须使用相同的名称
            instanceName: ClusterQuartz
            # ID设置为自动获取 每一个必须不同
            instanceId: AUTO
          #配置JobStore
          jobStore:
            # 是否加入集群
            isClustered: true
            # 数据保存方式为数据库持久化
            class: org.quartz.impl.jdbcjobstore.JobStoreTX
            #数据库代理类,一般org.quartz.impl.jdbcjobstore.StdJDBCDelegate可以满足大部分数据库
            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
            # 表的前缀,默认QRTZ_
            tablePrefix: QRTZ_
            # 保证待执行的任务是锁定的,持有锁超时,数据库会自动释放
            acquireTriggersWithinLock: true
            # 调度实例失效的检查时间间隔 ms   检查集群的状态间隔
            clusterCheckinInterval: 5000
            # 事务的隔离级别 推荐使用默认级别 设置为true容易造成死锁和不可重复读的一些事务问题
            txIsolationLevelSerializable: false
          #配置ThreadPool
          threadPool:
            # 线程池的实现类(一般使用SimpleThreadPool即可满足几乎所有用户的需求)
            class: org.quartz.simpl.SimpleThreadPool
            #指定线程数,一般设置为1-100直接的整数,根据系统资源配置
            threadCount: 4
            # 线程优先级 默认值为Thread.NORM_PRIORITY(5)
            threadPriority: 5
            threadsInheritContextClassLoaderOfInitializingThread: true
#下面还有Druid数据源配置

QuartzConfig.java增加数据源配置

@Configuration
public class QuartzConfig {

    /**
     * 注入数据源
     */
    @Resource
    private DataSource dataSource;

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() {
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        //指定JobFactory,用于自定义Job中注入SpringBean
        schedulerFactoryBean.setJobFactory(new SpringBeanJobFactory());
        // QuartzScheduler启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录
        schedulerFactoryBean.setOverwriteExistingJobs(true);
        schedulerFactoryBean.setApplicationContextSchedulerContextKey("applicationContextKey");
        //设置全局任务监听器
        schedulerFactoryBean.setGlobalJobListeners(new QuartzJobListener());
        //设置自动启动, 默认就是true,这里可以不设置
        //如果设置了false,SpringBoot应用启动的时候就不会自动去加载数据库里面的任务执行
        schedulerFactoryBean.setAutoStartup(false);

        //如果需要使用官方提供的11张表,则需要配置数据源
        //使用Spring配置的dataSource替换quartz的dataSource
        schedulerFactoryBean.setDataSource(dataSource);
        return schedulerFactoryBean;
    }
    .......

4.3 修改SheduleServiceImpl

新增定时器

 @Override
    public void saveShedule(SheduleDTO sheduleDTO) throws Exception {
        Shedule shedule = new Shedule();
        BeanUtil.copyProperties(sheduleDTO, shedule);
        this.addScheduleJob(shedule);
        // 保存任务信息到自定义表
       // this.save(shedule);
    }

就是把原来操作自定表的代码去掉,其他方法也是稍微修改下就能使用。
稍微修改下原来的恢复、暂停、删除定时任务方法。

增加查询所有任务方法

    /**
     * 查询所有任务
     *
     * @return
     */
    @Override
    public List<SheduleDTO> listAllJobs()  throws  SchedulerException{
        List<SheduleDTO> sheduleDTOList = new ArrayList<>();

        Set<JobKey> jobKeys = quartzScheduler.getJobKeys(null);
        jobKeys.forEach(jobKey ->{
            SheduleDTO sheduleDTO = new SheduleDTO();
            sheduleDTO.setSheduleCode(jobKey.getName());
            sheduleDTO.setSheduleGroup(jobKey.getGroup());
            try {
                sheduleDTO.setSheduleClassname(quartzScheduler.getJobDetail(jobKey).getJobClass().getName());
                List<? extends Trigger> triggers = quartzScheduler.getTriggersOfJob(jobKey);
                sheduleDTO.setSheduleCron(((CronTrigger)triggers.get(0)).getCronExpression());
                sheduleDTO.setSheduleStatus(quartzScheduler.getTriggerState(((CronTrigger) triggers.get(0)).getKey()).toString());
            } catch (SchedulerException e) {
                e.printStackTrace();
            }

            sheduleDTOList.add(sheduleDTO);
        });
        return sheduleDTOList;
    }

SheduleController.java增加方法

 @GetMapping("/all")
    @ApiOperation("查询出集群配置下的所有定时任务")
    public List<SheduleDTO> listAllJobs() throws SchedulerException {
        return sheduleService.listAllJobs();
    }

4.4 去除QuartzJobInitListener

默认情况下SchedulerFactoryBean.setAutoStartup(true);SpringBoot应用启动的时候就会自动去加载数据库里面的任务执行

4.5 Swagger调试

在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值