SpringBoot集成Quartz发布、修改、暂停、删除定时任务

一、基本概念

Quartz核心的概念:scheduler任务调度、Job任务、Trigger触发器、JobDetail任务细节。

scheduler任务调度:

是最核心的概念,需要把JobDetail和Trigger注册到scheduler中,才可以执行。

Job任务:

其实Job是接口,其中只有一个execute方法:

Trigger触发器

a)作用:它是来执行工作任务,在什么条件下触发,什么时间执行,多久执行一次。

b)四大类型:SimpleTrigger,CronTirgger,DateIntervalTrigger, 和 NthIncludedDayTrigger。

SimpleTrigger 一般用于实现每隔一定时间执行任务,以及重复多少次,如每 2 小时执行一次,重复执行 5 次。SimpleTrigger 内部实现机制是通过计算间隔时间来计算下次的执行时间,这就导致其不适合调度定时的任务。例如我们想每天的 1:00AM 执行任务,如果使用 SimpleTrigger 的话间隔时间就是一天。注意这里就会有一个问题,即当有 misfired 的任务并且恢复执行时,该执行时间是随机的(取决于何时执行 misfired 的任务,例如某天的 3:00PM)。这会导致之后每天的执行时间都会变成 3:00PM,而不是我们原来期望的 1:00AM。

CronTirgger 类似于 LINUX 上的任务调度命令 crontab,即利用一个包含 7 个字段的表达式来表示时间调度方式。例如,"0 15 10 * * ? *" 表示每天的 10:15AM 执行任务。对于涉及到星期和月份的调度,CronTirgger 是最适合的,甚至某些情况下是唯一选择。例如,"0 10 14 ? 3 WED" 表示三月份的每个星期三的下午 14:10PM 执行任务。读者可以在具体用到该 trigger 时再详细了解每个字段的含义

整合案例

项目结构


1562652375822.png

listener的作用

每次项目重启后,让原来处于运行中的任务,继续运行

QuartzManager的作用

对定时任务执行具体操作的工具类

依赖

 <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.3.1</version>
        </dependency>
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz-jobs</artifactId>
            <version>2.3.1</version>
        </dependency>

application.yml配置

#quartz
spring:
  quartz:
    scheduler-name: my-project-scheduler
    job-store-type: jdbc
    auto-startup: false
    wait-for-jobs-to-complete-on-shutdown: false
    overwrite-existing-jobs: false
    jdbc:
      #druid的wall filter会影响这里的自动建表
      initialize-schema: always
    properties:
      org:
        quartz:
          scheduler:
            instanceId: AUTO
            wrapJobExecutionInUserTransaction: false
            rmi:
              export: false
              proxy: false
          threadPool:
            class: org.quartz.simpl.SimpleThreadPool
            threadCount: 10
            threadPriority: 5
            threadsInheritContextClassLoaderOfInitializingThread: true
          jobStore:
            class: org.quartz.impl.jdbcjobstore.JobStoreTX
            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
            useProperties: true
            tablePrefix: QRTZ_
            misfireThreshold: 60000

quartzManager工具类

package com.nanc.modules.schedule.util;
import com.nanc.modules.schedule.entity.ScheduleJobInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Component;

@Component
@Slf4j
public class QuartzManager {
    @Autowired
    private SchedulerFactoryBean schedulerFactoryBean;

    public static final String JOB_GROUP_NAME = "EXTJWEB_JOBGROUP_NAME";
    public static final String TRIGGER_GROUP_NAME = "EXTJWEB_TRIGGERGROUP_NAME";

    /**
     * 添加任务,使用任务组名(不存在就用默认的),触发器名,触发器组名
     * 并启动
     * @param info
     */
    public void addJob(ScheduleJobInfo info) {
        try {
            Scheduler scheduler = schedulerFactoryBean.getScheduler();

            JobKey jobKey = JobKey.jobKey(info.getJobName(), info.getGroupName());
            JobDetail jobDetail = scheduler.getJobDetail(jobKey);
            if (null != jobDetail) {
                log.info("{}, {} 定时任务已经存在", info.getJobName(), info.getGroupName());
                return;
            }

            JobDataMap map = new JobDataMap();
            map.put("taskData", "测试的存放的数据");

            // JobDetail 是具体Job实例
            jobDetail = JobBuilder.newJob((Class<? extends Job>) Class.forName(info.getClassName()))
                    .withIdentity(info.getJobName(),info.getGroupName())
                    .usingJobData(map)
                    .build();

            // 基于表达式构建触发器
            CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(info.getCron());
            // CronTrigger表达式触发器 继承于Trigger
            // TriggerBuilder 用于构建触发器实例
            CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(info.getJobName()+"_trigger", TRIGGER_GROUP_NAME)
                    .withSchedule(cronScheduleBuilder).build();

            scheduler.scheduleJob(jobDetail, cronTrigger);

            //启动
            if (!scheduler.isShutdown()) {
                scheduler.start();
            }
        } catch (SchedulerException | ClassNotFoundException e) {
            log.error("添加任务失败",e);
        }
    }

    /**
     * 暂停任务
     * @param info
     */
    public void pauseJob(ScheduleJobInfo info) {
        try{
            Scheduler scheduler = schedulerFactoryBean.getScheduler();
            JobKey jobKey = JobKey.jobKey(info.getJobName(), info.getGroupName());
            scheduler.pauseJob(jobKey);
            log.info("=========================pause job: {} success========================", info.getJobName());
        }catch (Exception e){
            log.error("",e);
        }
    }

    /**
     * 恢复任务
     * @param info
     */
    public void resumeJob(ScheduleJobInfo info) {
        try {
            Scheduler scheduler = schedulerFactoryBean.getScheduler();
            JobKey jobKey = JobKey.jobKey(info.getJobName(), info.getGroupName());
            scheduler.resumeJob(jobKey);
            log.info("=========================resume job: {} success========================", info.getJobName());
        }catch (Exception e){
            log.error("",e);
        }
    }

    /**
     * 删除任务,在业务逻辑中需要更新库表的信息
     * @param info
     * @return
     */
    public boolean removeJob(ScheduleJobInfo info) {
        boolean result = true;
        try {

            Scheduler scheduler = schedulerFactoryBean.getScheduler();
            JobKey jobKey = JobKey.jobKey(info.getJobName(), info.getGroupName());
            JobDetail jobDetail = scheduler.getJobDetail(jobKey);
            if (null != jobDetail) {
                result =  scheduler.deleteJob(jobKey);
            }
            log.info("=========================remove job: {} {}========================", info.getJobName(), result);
        }catch (Exception e){
            log.error("",e);
            result=false;
        }
        return result;
    }

    /**
     * 修改定时任务的时间
     * @param info
     * @return
     */
    public boolean modifyJobTime(ScheduleJobInfo info){
        boolean result = true;
        try{
            Scheduler scheduler = schedulerFactoryBean.getScheduler();
            TriggerKey triggerKey = TriggerKey.triggerKey(info.getJobName() + "_trigger", TRIGGER_GROUP_NAME);
            CronTrigger trigger = (CronTrigger)scheduler.getTrigger(triggerKey);

            String oldTime = trigger.getCronExpression();
            if (!StringUtils.equalsIgnoreCase(oldTime, info.getCron())) {
                CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(info.getCron());
                CronTrigger ct = TriggerBuilder.newTrigger().withIdentity(info.getJobName()+ RandomStringUtils.randomAlphabetic(6) + "_trigger", TRIGGER_GROUP_NAME)
                        .withSchedule(cronScheduleBuilder)
                        .build();

                scheduler.rescheduleJob(triggerKey, ct);
                scheduler.resumeTrigger(triggerKey);
            }

        }catch (Exception e){
            log.error("", e);
            result=false;
        }
        return result;
    }

    /**
     * 启动所有定时任务
     */
    public void startJobs() {
        try {
            Scheduler scheduler = schedulerFactoryBean.getScheduler();
            scheduler.start();
        } catch (SchedulerException e) {
            log.error("",e);
        }
    }
}

定义的实体类

package com.nanc.modules.schedule.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.validation.constraints.NotEmpty;
import java.io.Serializable;
import java.util.Date;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@TableName("schedule_job_info")
@ApiModel(description = "定时任务实体类")
public class ScheduleJobInfo implements Serializable {
    private static final long serialVersionUID = -1465015133146616824L;

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

    /**
     * 任务group的名称
     */
    @NotEmpty(message = "任务group的名称不能为空")
    @ApiModelProperty(value = "任务group的名称", example = "test-group")
    private String groupName;

    /**
     * 任务job的名称
     */
    @NotEmpty(message = "任务job的名称不能为空")
    @ApiModelProperty(value = "任务job的名称", required = true, example = "test-job")
    private String jobName;

    /**
     * 定时任务标识
     */
    @NotEmpty(message = "定时任务标识不能为空")
    @ApiModelProperty(value = "任务的标识", required = true, example = "schedule-job-code")
    private String code;

    /**
     * cron 表达式
     */
    @NotEmpty(message = "定时任务标识不能为空")
    @ApiModelProperty(value = "cron表达式", required = true, example = "*/5 * * * * ?")
    private String cron;

    /**
     * 定时任务执行类
     */
    @NotEmpty(message = "定时任务标识不能为空")
    @ApiModelProperty(value = "定时任务执行类", required = true, example = "com.nanc.modules.schedule.job.CronTestJob")
    private String className;

    /**
     * 成功执行次数
     */
    @ApiModelProperty(hidden = true)
    private Integer succeed;

    /**
     * 失败执行次数
     */
    @ApiModelProperty(hidden = true)
    private Integer fail;

    /**
     * 任务的状态
     *      0 - 代表正在执行
     *      1 - 已删除
     *      2 - 暂停
     */
    @ApiModelProperty(hidden = true)
    private Integer status;


    /**
     * 任务创建的时间
     */
    @ApiModelProperty(hidden = true)
    private Date createTime;


    /**
     * 任务修改的时间
     */
    @ApiModelProperty(hidden = true)
    private Date updateTime;
}

控制层代码

package com.nanc.modules.schedule.controller;
import com.nanc.common.utils.R;
import com.nanc.modules.schedule.entity.ScheduleJobInfo;
import com.nanc.modules.schedule.service.ScheduleJobService;
import com.nanc.modules.schedule.util.QuartzManager;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
@Slf4j
@RestController
@RequestMapping("/quartz")
@Api(value = "定时任务", tags = "定时任务", position = 5)
public class ScheduleJobController {
    @Autowired
    private QuartzManager quartzManager;
    @Autowired
    private ScheduleJobService scheduleJobService;

    @ApiOperation(value = "添加定时任务并启动", notes = "添加定时任务并启动")
    @PostMapping("/add")
    public R<Boolean> addAndStartJob(@Valid @RequestBody ScheduleJobInfo info){
        boolean b = scheduleJobService.addAndStartJob(info);
        return R.ok(b);
    }

    /**
     * 暂停任务的运行
     * @param id
     * @return
     */
    @ApiOperation(value = "暂停任务", notes = "暂停任务")
    @PostMapping("/pause/{id}")
    public R<Boolean> pauseJob(@PathVariable(value = "id") Integer id) {
        boolean b = scheduleJobService.pauseJob(id);
        return R.ok(b);
    }

    /**
     * 暂停任务的运行
     * @param id
     * @return
     */
    @ApiOperation(value = "恢复任务", notes = "恢复任务")
    @PostMapping("/resume/{id}")
    public R<Boolean> resumeJob(@PathVariable(value = "id") Integer id) {
        boolean b = scheduleJobService.resumeJob(id);
        return R.ok(b);
    }

    /**
     * 删除任务
     * @param id
     * @return
     */
    @ApiOperation(value = "删除任务", notes = "删除任务")
    @PostMapping("/remove/{id}")
    public R<Boolean> removeJob(@PathVariable(value = "id") Integer id) {
        boolean b = scheduleJobService.removeJob(id);
        return R.ok(b);
    }

    /**
     * 修改定时任务的时间
     * @param id
     * @param cron
     * @return
     */
    @ApiOperation(value = "修改任务时间", notes = "修改任务时间")
    @PostMapping("/modify")
    public R<Boolean> modifyJobTime(@RequestParam(value = "id")Integer id,
                                    @RequestParam(value = "cron")String cron){

        boolean b = scheduleJobService.modifyJobTime(id, cron);
        return R.ok(b);
    }
}

关于swagger的整合,可以看另一篇文章

目前只是简单的实现了部分常用的方法

需要源代码的私信,我会从项目中把这模块提取出来,单独打包发送

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 226,913评论 6 527
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 97,710评论 3 412
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 174,561评论 0 373
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 62,278评论 1 306
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 71,080评论 6 405
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 54,604评论 1 320
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 42,698评论 3 434
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 41,847评论 0 285
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 48,361评论 1 329
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 40,330评论 3 353
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 42,487评论 1 365
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 38,044评论 5 355
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 43,738评论 3 342
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 34,134评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 35,378评论 1 281
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 51,053评论 3 385
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 47,471评论 2 370

推荐阅读更多精彩内容