Spring Boot整合Quartz

267 阅读2分钟

一、Quartz简介

  Quartz是一个开源作业调度框架,其核心概念包括:

  • Job:表示一个工作,要执行的具体内容。
  • JobDetail:表示一个具体的可执行的调度程序,包含任务调度的方案和策略。
  • Trigger:表示一个调度参数的配置,表示什么时候去调度。
  • Scheduler:表示一个调度容器,当JobDetail和Trigger组合时方可进行调度。
  • JobDataMap:JobDataMap中可以包含不限量的(序列化的)数据对象,在job实例执行的时候,可以使用其中的数据(JobDetail & Trigger)。在Job执行时,JobExecutionContext中的JobDataMap是JobDetail中的JobDataMap和Trigger中的JobDataMap的并集,但是如果存在相同的数据,则后者会覆盖前者的值。

图1-1 Quartz核心组件.png

//JobDataMap使用
.usingJobData("name", "luffy")
.usingJobData("age", 18)
/**
* 获取JobDetail和Trigger创建时的JobDataMap
*/
JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();

  @DisallowConcurrentExecution:将该注解加到job类上,告诉Quartz不要并发地执行同一个job定义(这里指特定的job类)的多个实例。(针对JobDetail

  @PersistJobDataAfterExecution:将该注解加在job类上,告诉Quartz在成功执行了job类的execute方法后(没有发生任何异常),更新JobDetail中JobDataMap的数据。(针对JobDetail

  Job.execute()方法中仅允许抛出一种类型的异常(包括RuntimeExceptions),即JobExecutionException。因此,你应该将execute方法中的所有内容都放到一个”try-catch"块中。job可以使用该异常告诉scheduler,你希望如何来处理发生的异常。

  SimpleTrigger:在具体的时间点执行一次,或者在具体的时间点执行,并且以指定的间隔重复执行若干次。

  CronTrigger:使用Cron Expressions指定触发策略。

  TriggerListeners、JobListeners、SchedulerListeners查看对应接口。

  Job Stores:切勿在代码中直接使用JobStore实例。

Quartz参考文档 Quartz配置参考

二、Spring Boot整合Quartz

  本文完整示例代码见

github.com/just-right/…

  引入maven依赖:

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

  Quartz相关配置参考:

spring-boot-2.0.3之quartz集成,不是你想的那样哦!

  自定义Job类实现execute方法,获取JobDetail和Trigger的dataMap属性。

@DisallowConcurrentExecution
@PersistJobDataAfterExecution
public class LuffyJob implements MyJob {
    private Logger logger = LoggerFactory.getLogger(getClass());
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        /**
         * 获取JobDetail和Trigger创建时的JobDataMap
         */
        JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
        JobDataMap mergedJobDataMap = jobExecutionContext.getMergedJobDataMap();
        logger.info("执行定时任务!");
        jobDataMap.forEach((k, v) -> logger.info(k + ":" + v));
        mergedJobDataMap.forEach((k, v) -> logger.info(k + ":" + v));
    }
}

  控制层传入Job和Trigger参数,开启调度器,创建JobDetai和Trigger。

@PostMapping(value = "/startJob")
public String startJob(@RequestBody StartJobDto dto) {
    String resInfo = null;
    Map<String,String> infoMap = new HashMap<>();
    infoMap.put("creator","luffy");
    JobDataMap dataMap = new JobDataMap(infoMap);
    try {
        //开启调度器
        scheduler.start();
        //创建JobDetail
        JobDetail jobDetail =  JobBuilder.newJob(getClass(dto.getJobClassName()).getClass())
                .withIdentity(dto.getJobName(), dto.getJobGroupName())
                .requestRecovery(true)
                .usingJobData("name","jobdetal")
                .usingJobData("age",2)
                .requestRecovery()
                .build();

        //创建CronScheduleBuilder
        CronScheduleBuilder scheduleBuilder =  CronScheduleBuilder.cronSchedule(dto.getCronExpression().trim());
        //创建触发器
        CronTrigger trigger =  TriggerBuilder.newTrigger()
                .withIdentity("trigger:"+dto.getJobName(), dto.getJobGroupName())
                .startNow()
                .usingJobData("name","trigger")
                .usingJobData("date",new Date().toString())
                .withSchedule(scheduleBuilder)
                .endAt(DateFormat.getDateTimeInstance().parse("2020-11-13 23:59:59"))
                .modifiedByCalendar("Holidays")
                .build();
        //调度器关联Job和Trigger
        HolidayCalendar cal = new HolidayCalendar();
        cal.addExcludedDate( DateFormat.getDateTimeInstance().parse("2020-11-13 23:59:59"));
        scheduler.addCalendar("Holidays",cal,false,false);
        scheduler.scheduleJob(jobDetail,trigger);
        resInfo = "success";
    } catch (Exception e) {
        resInfo = "error";
    }
    return resInfo;
}

三、异常、中断处理

3.1 异常处理

  立即重新执行任务:

@DisallowConcurrentExecution
@PersistJobDataAfterExecution
public class LuffyJob implements MyJob {
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        try {
           //do something
        } catch (Exception e) {
            JobExecutionException exception = new JobExecutionException(e);
            /**
             * 工作立即重新开始
             */
            exception.setRefireImmediately(true);
            throw exception;
        }
    }
}

   立即停止所有相关这个任务的触发器:

@DisallowConcurrentExecution
@PersistJobDataAfterExecution
public class LuffyJob implements MyJob {
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        try {
           //do something
        } catch (Exception e) {
            JobExecutionException exception = new JobExecutionException(e);
            /**
             * 自动取消与此作业关联的所有触发器计划
             */
            exception.setUnscheduleAllTriggers(true);
            throw exception;
        }
    }
}

Quartz:Quartz任务异常处理方式

3.2 中断处理

   实现InterruptableJob接口,重写execute和interrupt方法即可。

quartz2.3.0(七)调度器中断任务执行,手动处理任务中断事件

四、测试

  前端传入Job类路径、jobName、jobGroupName和cronExpression。

图4-1 Quartz请求测试.png

  每10秒打印一次dataMap信息,打印结果如下所示:

图4-2 Quartz输出结果信息.png