【Quartz】初识与基本使用

个人学习笔记分享,当前能力有限,请勿贬低,菜鸟互学,大佬绕道

如有勘误,欢迎指出和讨论,本文后期也会进行修正和补充


1.介绍

Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,完全由Java开发,可以用来执行定时任务,类似于java.util.Timer。但是相较于Timer, Quartz增加了很多功能:

  • 持久性作业 - 就是保持调度定时的状态;
  • 作业管理 - 对调度作业进行有效的管理;

使用quartz的调度器统一管理定时任务,可以动态的添加、删除、暂停等

推荐几个博客,写的真的挺好

2.使用步骤

2.1.spring框架使用方法

仅适用于spring基础框架,因为需要在web.xml里配置路径,然而springboot框架基本不用这个了。。。

配置方法很简单,但是建议使用springboot方法进行配置,与时俱进嘛

而且spring下的动态定时器巨麻烦,这里只做静态定时器的介绍,动态的使用springboot吧

  1. 添加quartz依赖

    <!-- 定时器依赖 -->
    <!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz -->
    <dependency>
        <groupId>org.quartz-scheduler</groupId>
        <artifactId>quartz</artifactId>
        <version>2.3.2</version>
    </dependency>
    
  2. 定义任务目标类,添加需要执行的任务内容

    里面定义一个需要执行的方法即可

    public class MyTask{
        /**
         * 计数器
         */
        private static Integer count = 0;
    
        /**
         * 执行内容
         *
         * @param jobExecutionContext 上下文
         */
        public void test() {
            synchronized (count) {
                System.out.println(new Date().toString() + ": " + count);
                count++;
            }
        }
    }
    
  3. 定义spring配置类spring-quartz.xml(当然名字可以自己随便取)

    1. 示例化任务目标类

      <bean id="myTask" class="com.yezi_tool.demo_basic.test.MyTask"/>
      
    2. 定义工作任务job,设置目标类、目标方法和是否允许并发

      <bean id="testJob" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
          <!-- 定时器的类  -->
          <property name="targetObject" ref="myTask"></property>
          <!-- 需要定时执行的方法  -->
          <property name="targetMethod" value="test"></property>
          <!-- 是否允许并发  -->
          <property name="concurrent" value="false"></property>
      </bean>
      
    3. 定义触发器trigger,定义工作任务job和执行时间(cron表达式可以在https://qqe2.com/cron 这里调试)

      <!-- 2.定义触发器Trigger并与Job绑定 -->
      <bean id="testJobTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
          <!-- 指定工作任务 -->
          <property name="jobDetail" ref="testJob"/>
          <!-- 根据需要设置定时执行的时间 -->
          <property name="cronExpression" value="*/1 * * * * ? " />
      </bean>
      
    4. 定义调度器,注册trigger

      <bean name="quartzScheduler" lazy-init="false" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
          <property name="triggers">
              <list>
                  <ref bean="testJobTrigger"/>
              </list>
          </property>
      </bean>
      

    完整xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean id="myTask" class="com.yezi_tool.demo_basic.test.MyTask"/>
        <!-- 1.定义工作任务job -->
        <bean id="testJob" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
            <!-- 指定任务类  -->
            <property name="targetObject" ref="myTask"></property>
            <!-- 需要定时执行的方法  -->
            <property name="targetMethod" value="test"></property>
            <!-- 是否允许并发  -->
            <property name="concurrent" value="false"></property>
        </bean>
        <!-- 2.定义触发器Trigger并与Job绑定 -->
        <bean id="testJobTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
            <!-- 指定工作任务 -->
            <property name="jobDetail" ref="testJob"/>
            <!-- 根据需要设置定时执行的时间 -->
            <property name="cronExpression" value="*/1 * * * * ? " />
        </bean>
    
        <!-- 3.定义调度器,并将trigger注册进去 -->
        <bean name="quartzScheduler" lazy-init="false" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
            <property name="triggers">
                <list>
                    <ref bean="testJobTrigger"/>
                </list>
            </property>
        </bean>
    </beans>
    
  4. 将xml的注入到项目

    • 基于spring的web项目,可以将所有配置文件添加到sprint-context.xml,然后将sprint-context.xml一起添加到web.xml

      1. 添加到spring-context.xml,其余内容都可有可无,不做赘述

        <import resource="classpath:spring-quartz.xml"/>
        
      2. 添加到web.xml

        <context-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>classpath:spring/spring-context.xml</param-value>
        </context-param>
        
    • 基于springboot,直接在quartz自定义配置类,或者项目启动类上添加引入注解即可

      //@ImportResource("classpath:*.xml")//引入所有xml
      @ImportResource("classpath:spring-quartz.xml")//引入quartz
      

2.2.基于springboot的集成方法

2.2.1.静态定时器

静态定时器即主要使用代码控制,一旦项目部署将无法对定时器做出调整(增删改),适用于强制执行而不需要灵活变通的任务

  1. 添加quartz依赖

    <!-- 定时器依赖 -->
    <!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz -->
    <dependency>
        <groupId>org.quartz-scheduler</groupId>
        <artifactId>quartz</artifactId>
        <version>2.3.2</version>
    </dependency>
    
  2. 创建需要执行的任务类MyTask,必须继承并实现QuartzJobBean

    public class MyTask extends QuartzJobBean {
        /**
         * 计数器
         */
        private static Integer count = 0;
    
        /**
         * 执行内容
         *
         * @param jobExecutionContext 上下文
         */
        @Override
        protected void executeInternal(JobExecutionContext jobExecutionContext) {
            JobDetail jobDetail = jobExecutionContext.getJobDetail();
            Trigger trigger = jobExecutionContext.getTrigger();
            synchronized (count) {
                System.out.println(""
                        + new Date()    //打印时间
                        + " :" + count  //打印计时器
                        + "  jobDetailKey : " + jobDetail.getKey().toString()   //打印jobDetail的key
                        + "  jobDetailData : " + jobDetail.getJobDataMap().getString("jobName") //打印来自jobDetail的参数
                        + "  triggerKey : " + trigger.getKey().toString()   //打印trigger的key
                        + "  triggerData : " + trigger.getJobDataMap().getString("triggerName") //打印来自trigger的参数
                );
                count++;
            }
        }
    }
    
  3. 创建quartz配置文件QuartzConfig.java

    1. 定义业务组件myJob1(),配置需要执行的任务类、业务自定义身份、转递给任务的参数等等
    2. 定义触发器myTrigger1(),配置关联的业务组件、触发器自定义身份、转递给任务的参数等等
    3. 别忘了使用@Configuration标记配置文件类、@Bean标记业务组件和触发器
    @Configuration
    public class QuartzConfig {
        @Bean
        public JobDetail myJob1() {
            JobDetail jobDetail = JobBuilder.newJob(MyTask.class)
                    .withIdentity("myJob1", "myJobGroup1")
                    //JobDataMap可以给任务execute传递参数,且可传递多个
                    .usingJobData("jobName", "myJob1")
                    .storeDurably()
                    .build();
            return jobDetail;
        }
    
        @Bean
        public Trigger myTrigger1() {
            Trigger trigger = TriggerBuilder.newTrigger()
                    .forJob(myJob1())
                    .withIdentity("myTrigger1", "myTriggerGroup1")
                    //JobDataMap可以给任务execute传递参数,且可传递多个
                    .usingJobData("triggerName", "myTrigger1")
                    .startNow()
                    //.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever())
                    .withSchedule(CronScheduleBuilder.cronSchedule("*/3 * * * * ?"))
                    .build();
            return trigger;
        }
    
        @Bean
        public Trigger myTrigger2() {
            Trigger trigger = TriggerBuilder.newTrigger()
                    .forJob(myJob1())
                    .withIdentity("myTrigger2", "myTriggerGroup1")
                    .usingJobData("triggerName", "myTrigger2")
                    .startNow()
                    .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever())
    //                .withSchedule(CronScheduleBuilder.cronSchedule("*/3 * * * * ?"))
                    .build();
            return trigger;
        }
    }
    

执行结果如下

image-20200908094332929

  • myTrigger1和myTrigger2均使用业务组件myJob1
  • myTrigger1每隔3秒触发一次
  • myTrigger2每隔5秒触发一次

2.2.2.动态定时器

动态定时器即可以对定时器动态进行调整,通常由前端页面和数据库配合,从而灵活调整定时器

因为需要动态管理,那么数据必须保证持久化,否则重新启动项目就什么都不剩了,那肯定不得行

添加quartz依赖

        <!-- 定时器依赖 -->
        <!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz -->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.3.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-quartz</artifactId>
        </dependency>

持久化配置文件

写入配置文件即可,仅编写quartz相关部分,其余按照自己需求即可,不影响

推荐下面两篇博文,介绍的很详细,此处只做简单实现

spring:
  quartz:
    #相关属性配置
    properties:
      org:
        quartz:
          scheduler:
            instanceName: quartzScheduler
            instanceId: AUTO
          jobStore:
            class: org.quartz.impl.jdbcjobstore.JobStoreTX
            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
            tablePrefix: QRTZ_
            isClustered: false
            clusterCheckinInterval: 10000
            useProperties: false
            misfireThreshold : 5000
          threadPool:
            class: org.quartz.simpl.SimpleThreadPool
            threadCount: 10
            threadPriority: 5
            threadsInheritContextClassLoaderOfInitializingThread: true
    #数据库方式
    job-store-type: JDBC
    #初始化表结构
    jdbc:
      initialize-schema: NEVER

建表

表需要依照quartz官方给出的实例,可以到http://www.quartz-scheduler.org/downloads/下载后解压,将目录\src\org\quartz\impl\jdbcjobstore\tables_mysql_innodb.sql里的语句在数据库直接执行即可

嫌卡或者懒得可以在这个地址获取2.3.0版本https://gitee.com/echo_ye/assets/blob/master/doc/tables_mysql_innodb.sql

数据封装类和查询方法

数据封装类需要包括前后端传递的所有参数,查询则需要向quartz相关的表中查询

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yezi_tool.demo_basic.mapper.JobMapper">
    <select id="listJob" resultType="com.yezi_tool.demo_basic.entity.QuartzJob">
        SELECT
          job.JOB_NAME as jobName,
          job.JOB_GROUP as jobGroup,
          job.DESCRIPTION as description,
          job.JOB_CLASS_NAME as jobClassName,
          cron.CRON_EXPRESSION as cronExpression,
          tri.TRIGGER_NAME as triggerName,
          tri.TRIGGER_STATE as triggerState,
          job.JOB_NAME as oldJobName,
          job.JOB_GROUP as oldJobGroup
        FROM qrtz_job_details AS job
        LEFT JOIN qrtz_triggers AS tri ON job.JOB_NAME = tri.JOB_NAME
        LEFT JOIN qrtz_cron_triggers AS cron ON cron.TRIGGER_NAME = tri.TRIGGER_NAME
        WHERE tri.TRIGGER_TYPE = 'CRON'
    </select>
</mapper>
package com.yezi_tool.demo_basic.mapper;

import com.yezi_tool.demo_basic.entity.QuartzJob;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface JobMapper {
    List<QuartzJob> listJob();
}

package com.yezi_tool.demo_basic.entity;

import lombok.Data;

import java.util.List;
import java.util.Map;

@Data
public class QuartzJob {
    private String jobName;//任务名称
    private String jobGroup;//任务分组
    private String description;//任务描述
    private String jobClassName;//执行类
    private String cronExpression;//执行时间

    private String triggerState;//任务状态

    private List<Map<String,Object>> dataMap;//参数
    
    private List<Map<String, Object>> jobDataParam;//备用数据域


    public QuartzJob() {
        super();
    }

    public QuartzJob(String jobName, String jobGroup, String description, String jobClassName, String cronExpression) {
        super();
        this.jobName = jobName;
        this.jobGroup = jobGroup;
        this.description = description;
        this.jobClassName = jobClassName;
        this.cronExpression = cronExpression;
    }
}

执行的任务类

与静态的任务类相同,可以建立多个,但是最好保证在同一个目录下,方便实例化的时候确认路径

本案例的任务类放置在目录com.yezi_tool.demo_basic.test

服务层方法

控制层为主要代码,在这里对定时任务进行增删改查等操作

因为懒就不写接口实现了。。。实际使用中请自己补上

重点为创建定时任务的方法。步骤如下

  1. 检查任务是否已存在,若是,则删除任务
  2. 根据任务类的名字(路径固定),实例化任务类task
  3. 创建定时任务job,设置目标任务类、job名、分组名、传递给任务的数据、任务描述等参数
  4. 创建触发器trigger,设置触发器的名字、分组、触发时间、传递给任务的数据等参数
  5. 通过调度程序schedule将job和trigger进行绑定并启动

也可在trigger中绑定job,那么schedule直接启动trigger即可

@Service
public class QuartzService {

    public String QUARTZ_TASK_PATH_HEAD = "com.yezi_tool.demo_basic.test" + ".";

    private final Scheduler scheduler;

    private final JobMapper jobMapper;

    public QuartzService(Scheduler scheduler, JobMapper jobMapper) {
        this.scheduler = scheduler;
        this.jobMapper = jobMapper;
    }

    public List<QuartzJob> list(String jobName) {
        List<QuartzJob> list = jobMapper.listJob();
        list.forEach(m -> {
            //只查询类名,无视路径
            m.setJobClassName(m.getJobClassName().replace(QUARTZ_TASK_PATH_HEAD, ""));
        });
        return list;
    }

    /**
     * 保存定时任务
     *
     * @param quartzJob 定时任务类
     * @throws Exception 抛出异常
     */
    public void save(QuartzJob quartzJob) throws Exception {
        try {
            //组装参数
            JobKey jobKey = new JobKey(quartzJob.getJobName(), quartzJob.getJobGroup());
            JobDataMap jobDataMap = new JobDataMap();
            if (quartzJob.getDataMap() != null) {
                for (Map<String, Object> map : quartzJob.getDataMap())
                    jobDataMap.putAll(map);
            }

            //删除旧的job
            if (scheduler.checkExists(jobKey)) {
                scheduler.deleteJob(jobKey);
            }

            //构建新的job
            Class clazz = Class.forName(QUARTZ_TASK_PATH_HEAD + quartzJob.getJobClassName());   //实例化目标任务类
            clazz.getDeclaredConstructor().newInstance();
            JobDetail jobDetail = JobBuilder
                    .newJob(clazz)//设置目标任务类
                    .withIdentity(quartzJob.getJobName(), quartzJob.getJobGroup())//设置job名和分组名
                    .usingJobData("jobName", quartzJob.getJobName())//传递的参数,可以自定义,可传递多组,以map形式传递
//                    .usingJobData(new JobDataMap(quartzJob.getDataMap()))
                    .withDescription(quartzJob.getDescription())//设置描述,可为空
                    .build();

            //构建新的触发器
            Trigger trigger = TriggerBuilder
                    .newTrigger()
//                    .forJob(jobDetail)//目标job,这里不设置,如果设置了将会自动触发
                    .withIdentity(quartzJob.getJobName() + "Trigger", quartzJob.getJobGroup())//设置job名和分组名
                    .usingJobData("triggerName", quartzJob.getJobName() + "Trigger")//传递的参数,可以自定义,可传递多组,以map形式传递
                    .usingJobData(jobDataMap)
                    .withSchedule(CronScheduleBuilder
                            .cronSchedule(quartzJob.getCronExpression().trim())
                            .withMisfireHandlingInstructionDoNothing()
                    )
                    .startNow()//现在就启动
//                    .startAt(new Date())//启动时间,可以自定义,与startNow冲突
                    .build();

            //通过schedule触发
            scheduler.scheduleJob(jobDetail, trigger);

        } catch (SchedulerException e) {
            throw new BaseException("保存定时任务失败");
        } catch (ClassNotFoundException e) {
            throw new BaseException("任务目标类不存在");
        }
    }


    /**
     * 启动任务
     *
     * @param jobName  任务名
     * @param jobGroup 任务分组
     * @param dataMap  数据,可为空
     * @throws Exception 相关异常
     */
    public void trigger(String jobName, String jobGroup, List<Map<String, Object>> dataMap) throws Exception {
        //组装参数
        JobKey jobKey = new JobKey(jobName, jobGroup);
        JobDataMap jobDataMap = new JobDataMap();
        if (dataMap != null) {
            for (Map<String, Object> map : dataMap)
                jobDataMap.putAll(map);
        }
        //启动触发器
        try {
            scheduler.triggerJob(jobKey, jobDataMap);
        } catch (SchedulerException e) {
            throw new BaseException("启动定时任务失败");
        }
    }

    /**
     * 暂停任务
     *
     * @param jobName  任务名
     * @param jobGroup 任务分组
     * @throws Exception 相关异常
     */
    public void pause(String jobName, String jobGroup) throws Exception {
        //组装参数
//        JobKey jobKey = new JobKey(jobName, jobGroup);
        TriggerKey triggerKey = TriggerKey.triggerKey(jobName + "Trigger", jobGroup);

        //暂停触发器
        try {
//            scheduler.pauseJob(jobKey);
            scheduler.pauseTrigger(triggerKey);
        } catch (SchedulerException e) {
            throw new BaseException("暂停定时任务失败");
        }
    }

    /**
     * 继续任务
     *
     * @param jobName  任务名
     * @param jobGroup 任务分组
     * @throws Exception 相关异常
     */
    public void resume(String jobName, String jobGroup) throws Exception {
        //组装参数
//        JobKey jobKey = new JobKey(jobName, jobGroup);
        TriggerKey triggerKey = TriggerKey.triggerKey(jobName + "Trigger", jobGroup);

        //继续触发器
        try {
//            scheduler.resumeJob(jobKey);
            scheduler.resumeTrigger(triggerKey);
        } catch (SchedulerException e) {
            throw new BaseException("启动定时任务失败");
        }
    }

    /**
     * 取消任务
     *
     * @param jobName  任务名
     * @param jobGroup 任务分组
     * @throws Exception 相关异常
     */
    public void cancel(String jobName, String jobGroup) throws Exception {
        try {
            TriggerKey triggerKey = TriggerKey.triggerKey(jobName + "Trigger", jobGroup);
            JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
            //暂停
            scheduler.pauseTrigger(triggerKey);
            //解除绑定
            scheduler.unscheduleJob(triggerKey);
            //删除触发器
            scheduler.deleteJob(jobKey);
        } catch (SchedulerException e) {
            throw new BaseException("取消定时任务失败");
        }
    }

    /**
     * 取消全部任务
     *
     * @throws Exception 相关异常
     */
    public void cancelAll() throws Exception {
        List<QuartzJob> jobList = jobMapper.listJob();
        for (QuartzJob quartzJob : jobList) {
            cancel(quartzJob.getJobName(), quartzJob.getJobGroup());
        }
    }

    /**
     * 暂停全部任务
     *
     * @throws Exception 相关异常
     */
    public void pauseAll() throws Exception {
        try {
            scheduler.pauseAll();
        } catch (SchedulerException e) {
            throw new BaseException("暂停定时任务失败");
        }
    }

    /**
     * 恢复所有任务
     *
     * @throws Exception
     */
    public void resumeAll() throws Exception {
        try {
            scheduler.resumeAll();
        } catch (SchedulerException e) {
            throw new BaseException("启动定时任务失败");
        }
    }

}

控制层

直接调用服务层方法就可以了,没啥技术含量

这里只调用部分功能用于测试,请按照实际需求调整

@Controller
@RequestMapping("/quartz")
@Slf4j
public class QuartzController {
    private QuartzService quartzService;

    public QuartzController(QuartzService quartzService) {
        this.quartzService = quartzService;
    }

    @GetMapping("/list")
    @ResponseBody
    public ReturnMsg list(String jobName) {
        log.info("查询列表");
        return ReturnMsg.success(quartzService.list(jobName));
    }

    @PostMapping("/save")
    @ResponseBody
    public ReturnMsg save(@RequestBody QuartzJob quartzJob) throws Exception {
        log.info("保存");
        quartzService.save(quartzJob);
        return ReturnMsg.success();
    }

    @PostMapping("/pauseAll")
    @ResponseBody
    public ReturnMsg pauseAll() throws Exception {
        log.info("全部暂停");
        quartzService.pauseAll();
        return ReturnMsg.success();
    }

    @PostMapping("/resumeAll")
    @ResponseBody
    public ReturnMsg resumeAll() throws Exception {
        log.info("恢复全部");
        quartzService.resumeAll();
        return ReturnMsg.success();
    }

    @PostMapping("/cancelAll")
    @ResponseBody
    public ReturnMsg cancelAll() throws Exception {
        log.info("删除全部");
        quartzService.cancelAll();
        return ReturnMsg.success();
    }
}


测试结果

save的参数如下,其余请求为空参数

{
"jobName": "job1",
"jobGroup": "group1",
"description": "demoJob",
"jobClassName": "MyTask",
"cronExpression": "*/3 * * * * ?"
}

2.2.3.补充

  • job与trigger的关系

    job表示任务,trigger表示触发器

    实际上两者是1-N的关系,一个任务可以被多个trigger持有,而trigger只能绑定一个job,但为了方便管理建议按1-1处理

    因此对于job的操作(暂停、恢复)会同步至与之关联的trigger,部分操作如下

    • 暂停/恢复job时,暂停/恢复与之相关的所有trigger
    • 删除job时,解除所有trigger与自身的关联,解除失败时报错

    而对于trigger的操作并不会同步到job

  • 执行的时间

    先上图

    image-20200909153729345

    可以看到trigger中自己生成了一个字段nextFireTime,即下一次执行时间

    到了nextFireTime时间则会执行一次任务,并将该字段更新

  • 执行失效的处理方案

    配置文件中有一条属性misfireThreshold = 5000,即失效阈值为5秒钟,表示一个任务若5秒内没有执行完成,则表示执行失效

    在对Trigger.withSchedule的时候对ScheduleBuilder设置处理方案

    • CronScheduleBuilder有4种方案,设置方法如下

      • 不设置:默认,智能处理。具体多智能我也不知道,但是这种不确定性显然是比较危险的,慎用
      • withMisfireHandlingInstructionIgnoreMisfires():忽略失效策略。服务恢复(包括项目启动)后,将一次性执行多次直至错过的任务全部补充。适用于部分场景。
      • withMisfireHandlingInstructionDoNothing():啥也不做。服务恢复后,将直接把nextFireTime更新至当前时间之后的下一次的执行时间,那么之前漏掉的任务将会被抛弃掉。适用于大部分场景。
      • withMisfireHandlingInstructionFireAndProceed():立即执行一次。服务恢复后立即执行一次,即将下次执行时间修改为现在,执行任务并从现在计算下一次时间。适用于部分场景。
    • SimpleScheduleBuilder有5种方案

    • 不设置:默认,智能处理,慎用

      • withMisfireHandlingInstructionIgnoreMisfires():忽略失效策略。服务恢复后,将一次性执行多次直至错过的任务全部补充。适用于部分场景。
      • withMisfireHandlingInstructionFireNow():立即执行一次,通常用于不重复执行的任务。即恢复后立即执行一次,对于不重复的任务相当于重试,对于重复的任务会立即执行一次,仅保留剩余次数而直接遗忘掉开始时间和总次数
    • withMisfireHandlingInstructionNextWithExistingCount():从当前时间之后的下次执行时间继续,错过的次数保留,不扣除

      • withMisfireHandlingInstructionNextWithRemainingCount():从当前时间之后的下次执行时间继续,错过的次数扣除
    • withMisfireHandlingInstructionNowWithExistingCount():从现在继续,错过的次数保留,不扣除。遗忘开始时间和总次数

      • withMisfireHandlingInstructionNowWithRemainingCount():从现在继续,错过的次数扣除。遗遗忘开始时间和总次数

    剩余两种ScheduleBuilder不多做描述,有兴趣的可以直接查源码,写的还是比较详细的

    若未设置misfireThreshold属性,以上方案均不会生效,依旧为智能处理,且大概率为忽略失效策略

  • pauseTrigger与pauseJob与pauseAll(resume同理)

    • pauseTrigger,即暂停触发器,那么自然任务也就不会再被触发
    • pauseJob,即暂停job,会将与自己关联的所有trigger一同暂停,resume同理
    • pauseAll,即全部暂停,实际上是暂停所有的group,并在数据库做记录,resume同理

2.2.4.可能遇到的坑

  • resume或重启项目后任务快速执行多次

    原因就是前面补充的,错过时间的处理方案,默认值是智能处理,而智能成啥样并不知道。。。建议主动设置为需要的模式

    如修改trigger构建方法如下

             //构建新的触发器
             Trigger trigger = TriggerBuilder
                     .newTrigger()
    //                    .forJob(jobDetail)//目标job,这里不设置,如果设置了将会自动触发
                     .withIdentity(quartzJob.getJobName() + "Trigger", quartzJob.getJobGroup())//设置job名和分组名
                     .usingJobData("triggerName", quartzJob.getJobName() + "Trigger")//传递的参数,可以自定义,可传递多组,以map形式传递
                     .usingJobData(jobDataMap)
                     .withSchedule(CronScheduleBuilder
                             .cronSchedule(quartzJob.getCronExpression().trim())
                             .withMisfireHandlingInstructionDoNothing()
                     )
                     .startNow()//现在就启动
    //                    .startAt(new Date())//启动时间,可以自定义,与startNow冲突
                     .build();
    
  • pauseAll之后删除,然后再建立一样的任务,任务默认为暂停状态,而不是自动启动

    pauseAll会暂停所有的triggerGroup并存储到表qrtz_paused_trigger_grps,而删除任务时并不会删除这张表里的内容(但会删除trigger)

    那么当从新建立一个一毛一样的任务的时候,quartz就会将其识别为pause状态,因而并不会启动

    至于解决办法。。。个人建议批量暂停不要用pauseAll实现,完全可以查出所有任务,然后挨个pauseJob


3.demo地址

https://gitee.com/echo_ye/demo_basic/tree/scheduleDemo

不同定时器启用方法在README.MD中查看,一共6种方法,如有纰漏请联系我

仅实现了部分功能作为样例,请按照需求自己扩展哦,有疑问或者建议欢迎联系我~


BB两句

quartz之前只在spring用过。。。本来以为就一个小组件,打算几个定时器整理在一起得了。。。结果越学越多。。不得不分离出来作为单独的文章

当然整理出来的依然只有一小部分,后面有机会再扩充整理一遍

而且讲道理quartz的注释是写的真的挺好,我这个英语渣渣跟着debug看源码也问题不大



作者:Echo_Ye

WX:Echo_YeZ

EMAIL :echo_yezi@qq.com

个人站点:在搭了在搭了。。。(右键 - 新建文件夹)

posted @ 2020-09-10 18:23  Echo_Ye  阅读(477)  评论(0编辑  收藏  举报