当前位置: 首页 > news >正文

使用Quartz或CronUtil实现动态的任务调度以及任务的管理

动态任务调度

文章目录

  • 动态任务调度
    • @[toc]
    • 1、使用Quartz实现任务的管理
      • 1.1 引入maven依赖
      • 1.2 创建定时任务类Job
      • 1.3 创建任务调度器(Scheduler)
      • 1.4 创建任务明细(JobDetail)
      • 1.5 创建触发器(Trigger)
      • 1.6 启动任务
      • 错误解决
        • Unable to store Job : ‘DEFAULT.TASK_1‘, because one already exists with this identification.定时任务报错
    • 2、使用hutool工具包中CronUtil实现(实现简单)
      • 2.1 引入maven 依赖
      • 2.2 创建单例CronUtil
      • 2.3 编写定时任务类
      • 2.4 编写任务管理代码

1、使用Quartz实现任务的管理

流程

  1. 首先需要创建我们的任务(Job),比如取消订单、定时发送短信邮件之类的,这是我们的任务主体,也是写业务逻辑的地方。
  2. 创建任务调度器(Scheduler),这是用来调度任务的,主要用于启动、停止、暂停、恢复等操作,也就是那几个api的用法。
  3. 创建任务明细(JobDetail),最开始我们编写好任务(Job)后,只是写好业务代码,并没有触发,这里需要用JobDetail来和之前创建的任务(Job)关联起来,便于执行。
  4. 创建触发器(Trigger),触发器是来定义任务的规则的,比如几点执行,几点结束,几分钟执行一次等等。这里触发器主要有两大类(SimpleTrigger和CronTrigger)。
  5. 根据Scheduler来启动JobDetail与Trigger

1.1 引入maven依赖

        <!--quartz管理定时任务-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-quartz</artifactId>
        </dependency>

1.2 创建定时任务类Job

@DisallowConcurrentExecution
//Job中的任务有可能并发执行,
// 例如任务的执行时间过长,而每次触发的时间间隔太短,
// 则会导致任务会被并发执行。如果是并发执行,
// 就需要一个数据库锁去避免一个数据被多次处理。
public class TestJob implements Job {

  @Override
  public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
    //任务的业务逻辑写在这里
    System.err.println(jobExecutionContext.getJobDetail().getJobDataMap().get("name"));
    System.err.println(jobExecutionContext.getJobDetail().getJobDataMap().get("param"));
    System.err.println(jobExecutionContext.getTrigger().getJobDataMap().get("orderNo"));
    System.out.println("-------------------\n\n     定时任务执行\n\n-------------\n");
  }
}

1.3 创建任务调度器(Scheduler)

@Autowired
private Scheduler scheduler;

1.4 创建任务明细(JobDetail)

/**通过JobBuilder.newJob()方法获取到当前Job的具体实现(以下均为链式调用)
* 这里是固定Job创建,所以代码写死XXX.class
* 如果是动态的,根据不同的类来创建Job,则 ((Job)Class.forName("com.zy.job.TestJob").newInstance()).getClass()
* 即是 JobBuilder.newJob(((Job)Class.forName("com.zy.job.TestJob").newInstance()).getClass())
* */
JobDetail jobDetail = JobBuilder.newJob(TestJob.class)
/**给当前JobDetail添加参数,K V形式*/
.usingJobData("name","zy")
/**给当前JobDetail添加参数,K V形式,链式调用,可以传入多个参数,在Job实现类中,可以通过jobExecutionContext.getJobDetail().getJobDataMap().get("age")获取值*/
.usingJobData("age",23)
/**添加认证信息,有3种重写的方法,我这里是其中一种,可以查看源码看其余2种*/
.withIdentity("我是name","我是group")
.build();//执行

1.5 创建触发器(Trigger)

这里主要分为两大类SimpleTrigger、CronTrigger。

SimpleTrigger:是根据它自带的api方法设置规则,比如每隔5秒执行一次、每隔1小时执行一次。

Trigger trigger = TriggerBuilder.newTrigger()
  /**给当前JobDetail添加参数,K V形式,链式调用,可以传入多个参数,在Job实现类中,可以通过jobExecutionContext.getTrigger().getJobDataMap().get("orderNo")获取值*/
  .usingJobData("orderNo", "123456")
  /**添加认证信息,有3种重写的方法,我这里是其中一种,可以查看源码看其余2种*/
  .withIdentity("我是name","我是group")
  /**立即生效*/
  //      .startNow()
  /**开始执行时间*/
  .startAt(start)
  /**结束执行时间,不写永久执行*/
  .endAt(start)
  /**添加执行规则,SimpleTrigger、CronTrigger的区别主要就在这里*/
  .withSchedule(
  SimpleScheduleBuilder.simpleSchedule()
  /**每隔3s执行一次,api方法有好多规则自行查看*/
  .withIntervalInSeconds(3)
  /**一直执行,如果不写,定时任务就执行一次*/
  .repeatForever()
)
  .build();//执行

CronTrigger:这就比较常用了,是基于Cron表达式来实现的。

CronTrigger  trigger = TriggerBuilder.newTrigger()
  /**给当前JobDetail添加参数,K V形式,链式调用,可以传入多个参数,在Job实现类中,可以通过jobExecutionContext.getTrigger().getJobDataMap().get("orderNo")获取值*/
  .usingJobData("orderNo", "123456")
  /**添加认证信息,有3种重写的方法,我这里是其中一种,可以查看源码看其余2种*/
  .withIdentity("orderNo","任务唯一信息")
  /**立即生效*/
  //      .startNow()
  /**开始执行时间*/
  .startAt(start)
  /**结束执行时间,不写永久执行*/
  .endAt(start)
  /**添加执行规则,SimpleTrigger、CronTrigger的区别主要就在这里,我这里是demo,写了个每2分钟执行一次*/
  .withSchedule(CronScheduleBuilder.cronSchedule("0 0/2 * * * ?"))
  .build();//执行

注意:.startNow( )和.startAt( )这里有个坑,这两个方法是对同一个成员变量进行修改的 也就是说startAt和startNow同时调用的时候任务开始的时间是按后面调用的方法为主的,谁写在后面用谁。

源码如下

public TriggerBuilder<T> startAt(Date triggerStartTime) {
this.startTime = triggerStartTime;
return this;
}

public TriggerBuilder<T> startNow() {
this.startTime = new Date();
return this;
}

1.6 启动任务

/**添加定时任务*/
scheduler.scheduleJob(jobDetail, trigger);
if (!scheduler.isShutdown()) {
  /**启动*/
  scheduler.start();
}

以上,任务的创建启动都完事了,后面就是任务的暂停、恢复、删除。比较简单,大致原理就是我们在创建任务明细(JobDetail)和创建触发器(Trigger)时,会调用.withIdentity(key,group)来传入认证信息,后续就是根据这些认证信息来管理任务(通过api方法)

1.7 任务的暂停pauseTrigger

scheduler.pauseTrigger(TriggerKey.triggerKey("orderNo","我是刚才写的group"));

1.8 任务的恢复resumeTrigger

scheduler.resumeTrigger(TriggerKey.triggerKey("orderNo","我是刚才写的group"));

1.9 任务的移除(暂停>移除触发器>删除Job)

scheduler.pauseTrigger(TriggerKey.triggerKey("orderNo","我是刚才写的group"));//暂停触发器
scheduler.unscheduleJob(TriggerKey.triggerKey("orderNo","我是刚才写的group"));//移除触发器
scheduler.deleteJob(JobKey.jobKey("orderNo","我是刚才写的group"));//删除Job

最后 附上动态调度封装好的方法

任务实体类

/**
 * Created by IntelliJ IDEA.
 * User: LvHaoIT (lvhao)
 * Date: 2022/9/23
 * Time: 11:51 定时任务信息
 */

@Data
@Slf4j
public class ScheduledTaskData {
    //    @TableId(value = "id", type = IdType.AUTO)
    @ApiModelProperty(value = "id")
    private String id;

    @ApiModelProperty(value = "任务名称")
    private String jobName;

    @ApiModelProperty(value = "任务路径")
    private String jobClass;

    @ApiModelProperty(value = "运行时间表达式")
    private String corn;

    public String getParamJson() {
        return JSON.toJSONString(this.param);
    }

    @ApiModelProperty(value = "是否禁用")
    private String disabled;

    @ApiModelProperty(value = "是否删除")
    private Integer isDel;


    @ApiModelProperty(value = "添加人")
    private String addUser;

    /**
     * 添加时间
     */
    @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @ApiModelProperty(value = "添加时间")
    private Date addTime;


    private Map<String, Object> param;

    public static void printFun(ScheduledTaskData scheduledTaskData, boolean b) {
        String info = "";
        if (b) {
            info = "启动成功";
        } else info = "停止成功";
        log.info("\n-------------------------------\n\n\t 定时任务:" + info + "\n" +
                "\t 任务名称 :" + scheduledTaskData.getJobName() + "\n" +
                "\t 任务ID :" + scheduledTaskData.getId() + "\n" +
                "\t 任务对象 :" + scheduledTaskData.getJobClass() + "\n" +
                "\t 运行时间 :" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "\n" +
                "\n\n-------------------------------");
    }
}

封装操作Api

package com.lingxu.module.AutoJob.controller;

import cn.hutool.cron.CronUtil;
import com.lingxu.module.AutoJob.entity.ScheduledTaskData;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.Date;

@Slf4j
@Api(tags = "Quartz任务调度")
@RestController
@RequestMapping("/quartz")
public class QuartzJob {
  @Resource
  private Scheduler scheduler;


  @Resource
  private CronUtil cronUtil;


  @PostMapping("/create")
  @ApiOperation(value = "定时任务_创建", notes = "创建")
  public Object quartz(@RequestBody ScheduledTaskData scheduledTaskData) throws Exception {

    Date start = new Date(System.currentTimeMillis() + 3 * 1000);
    JobDetail jobDetail = JobBuilder.newJob(((Job) Class.forName(scheduledTaskData.getJobClass()).newInstance()).getClass())
      .usingJobData("name", "aaa")
      .usingJobData("param", scheduledTaskData.getParamJson())
      .withIdentity(scheduledTaskData.getId())
      .build();

    CronTrigger trigger = TriggerBuilder.newTrigger()
      .usingJobData("orderNo", scheduledTaskData.getId())
      .withIdentity(scheduledTaskData.getId())
      .startAt(start)
      .withSchedule(CronScheduleBuilder.cronSchedule(scheduledTaskData.getCorn()))
      .build();

    scheduler.scheduleJob(jobDetail, trigger);
    if (!scheduler.isShutdown()) {
      scheduler.start();
      ScheduledTaskData.printFun(scheduledTaskData, true);
    } else {
      scheduler.shutdown(true);
      scheduler.pauseTrigger(TriggerKey.triggerKey(scheduledTaskData.getId()));
      scheduler.unscheduleJob(TriggerKey.triggerKey(scheduledTaskData.getId()));
      scheduler.deleteJob(JobKey.jobKey(scheduledTaskData.getId()));
      scheduler.start();
      ScheduledTaskData.printFun(scheduledTaskData, true);
    }
    return "ok";
  }


  @PostMapping("/shutdown")
  @ApiOperation(value = "定时任务_停止", notes = "停止")
  @ResponseBody
  public Object shutdown(@RequestParam("orderNo") String orderNo) throws IOException, SchedulerException {
    scheduler.pauseTrigger(TriggerKey.triggerKey(orderNo));
    return "";
  }

  @PostMapping("/resume")
  @ApiOperation(value = "定时任务_恢复", notes = "恢复")
  @ResponseBody
  public Object resume(@RequestParam("orderNo") String orderNo) throws IOException, SchedulerException {
    scheduler.resumeTrigger(TriggerKey.triggerKey(orderNo));
    return "ok";
  }

  @PostMapping("/del")
  @ApiOperation(value = "定时任务_删除", notes = "删除")
  @ResponseBody
  public Object del(@RequestParam("orderNo") String orderNo) throws IOException, SchedulerException {
    scheduler.pauseTrigger(TriggerKey.triggerKey(orderNo));
    scheduler.unscheduleJob(TriggerKey.triggerKey(orderNo));
    scheduler.deleteJob(JobKey.jobKey(orderNo));
    return "ok";
  }
}

如果需要让定时任务在启动项目后自动启动,则需要持久化任务,可以把基本信息保存在数据库中,项目启动时循环启动

错误解决

Unable to store Job : ‘DEFAULT.TASK_1‘, because one already exists with this identification.定时任务报错

原因: 是Quartz框架对应的数据库表格的问题(因为任务并未能正常结束,产生了脏数据),我们只需要删除以下三个好了。

DELETE from QRTZ_CRON_TRIGGERS;
DELETE from QRTZ_TRIGGERS;
DELETE from QRTZ_JOB_DETAILS;

2、使用hutool工具包中CronUtil实现(实现简单)

hutool的定时任务模块与Linux的Crontab使用上非常类似,通过一个cron.setting配置文件,简单调用start()方法即可简单使用。

官方文档介绍:hutool官方文档

2.1 引入maven 依赖

<dependency> 
  <groupId>cn.hutool</groupId> 
  <artifactId>hutool-cron</artifactId>
  <version>5.5.6</version>
</dependency>

2.2 创建单例CronUtil

@Component
public class CronUtiObject {
    @Bean
    public CronUtil getCronUtil() {
        CronUtil cronUtil = new CronUtil();
        return cronUtil;
    }
}

2.3 编写定时任务类

需要实现Task接口

package com.lingxu.module;
import cn.hutool.cron.task.Task;

/**
 * Created by IntelliJ IDEA.
 * User: LvHaoIT (lvhao)
 * Date: 2022/9/23
 * Time: 16:29
 */
public class TestTask implements Task {
    @Override
    public void execute() {
        System.out.println("!!!!!    2s定时任务开始");
    }
}

2.4 编写任务管理代码

1.  新增任务 `CronUtil.schedule(任务编号, cron表达式 "*/2 * * * * *", 定时任务实例)`
2.  启动定时任务 `CronUtil.start();`
3.  移除定时任务 `CronUtil.remove(任务编号);`
4.  停止任务 `CronUtil.stop();`
@Test
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {

  //        CronUtil.schedule("abc", "*/2 * * * * *", new TestTask());
  CronUtil.schedule("abc", "*/2 * * * * *", (Task) Class.forName("com.lingxu.module.TestTask").newInstance());


  CronUtil.setMatchSecond(true);
  CronUtil.start();
  System.out.println("cronUtil已经启动");

  CronUtil.schedule("aaa", "*/10 * * * * *", new Task() {
    public void execute() {
      System.out.println("===== 10 s定时任务开始");
    }
  });

  ThreadUtil.sleep(12000);

  CronUtil.remove("abc");
  //        CronUtil.stop();
}

以上两种都可以实现定时任务的动态调度,Quartz更灵活但实现也比较复杂,CronUtil实现起来较为简单,单管理功能偏少。

相关文章:

  • 死磕它3年,入职京东,要个25K不过分吧?
  • 乐高CPC认证办理儿童玩具出口美国亚马逊CPSIC认证
  • .NET Core Web APi类库如何内嵌运行?
  • Kafka3.2.3基于Linux的集群安装(待续)
  • 数据湖技术之 Hudi 框架概述
  • 前端利器 —— 提升《500倍开发效率》 传一张设计稿,点击一建生成项目 好牛
  • MySQL数据库基础:数据类型详解-数值类型
  • 超好用的内网穿透工具【永久免费不限制流量】
  • Doris0.15平滑升级至1.12
  • CentOS系统安装Docker Engine
  • 【Node.js 入门篇】连接 MySQL
  • 一文带你读懂Vue生命周期
  • Java顺序表的实现
  • 金仓数据库KingbaseES数据库参考手册(服务器配置参数18. 开发者选项)
  • mysql 跨库数据清洗方案
  • [ JavaScript ] 数据结构与算法 —— 链表
  • Android 控件背景颜色处理
  • Android框架之Volley
  • C++11: atomic 头文件
  • canvas 高仿 Apple Watch 表盘
  • CSS3 变换
  •  D - 粉碎叛乱F - 其他起义
  • E-HPC支持多队列管理和自动伸缩
  • Hexo+码云+git快速搭建免费的静态Blog
  • IIS 10 PHP CGI 设置 PHP_INI_SCAN_DIR
  • JS基础篇--通过JS生成由字母与数字组合的随机字符串
  • nginx 配置多 域名 + 多 https
  • React系列之 Redux 架构模式
  • Spring技术内幕笔记(2):Spring MVC 与 Web
  • Vue.js源码(2):初探List Rendering
  • 表单中readonly的input等标签,禁止光标进入(focus)的几种方式
  • 前端面试题总结
  • 前端学习笔记之观察者模式
  • 三栏布局总结
  • 我的业余项目总结
  • scrapy中间件源码分析及常用中间件大全
  • # 达梦数据库知识点
  • (2)Java 简介
  • (3)llvm ir转换过程
  • (阿里巴巴 dubbo,有数据库,可执行 )dubbo zookeeper spring demo
  • (附源码)计算机毕业设计SSM基于java的云顶博客系统
  • (全注解开发)学习Spring-MVC的第三天
  • (原创) cocos2dx使用Curl连接网络(客户端)
  • (转)c++ std::pair 与 std::make
  • (转)Java socket中关闭IO流后,发生什么事?(以关闭输出流为例) .
  • (转)jQuery 基础
  • (转)大道至简,职场上做人做事做管理
  • .NET 8 编写 LiteDB vs SQLite 数据库 CRUD 接口性能测试(准备篇)
  • .net core 实现redis分片_基于 Redis 的分布式任务调度框架 earth-frost
  • .Net Framework 4.x 程序到底运行在哪个 CLR 版本之上
  • .NET项目中存在多个web.config文件时的加载顺序
  • [] 与 [[]], -gt 与 > 的比较
  • [28期] lamp兄弟连28期学员手册,请大家务必看一下
  • [BUUCTF]-Reverse:reverse3解析
  • [C++]C++基础知识概述