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

动态任务调度

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

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

扫描二维码关注公众号,回复: 14709688 查看本文章

任务实体类

/**
 * 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实现起来较为简单,单管理功能偏少。

猜你喜欢

转载自blog.csdn.net/qq_27331467/article/details/127013955