grails4.0.1配置Quartz定时任务

2023-10-29

目录

Job、Trigger、Schedule 使用

深入理解 Scheduler、Job、Trigger、JobDetail

Quartz API 几个重要接口:

Scheduler 调度程序

SchedulerFactory 调度程序工厂

Job 定时任务实例类

Job 实例化的过程

Trigger 触发器

JobBuilder 用于创建 JobDetail,TriggerBuilder 用于创建触发器 Trigger

Cron 表达式的使用

简单触发器 SimpleTrigger

关于简单触发器“熄火(misfire)”的指令

基于 Cron 表达式的触发器 CronTrigger

Cron 表达式

创建 CronTrigger

关于 Cron 触发器“熄火”的指令


 

Job、Trigger、Schedule 使用

第一步:编写一个 job 类,需要实现 org.quartz.Job 接口

import org.quartz.Job; 
import org.quartz.JobExecutionContext; 
import org.quartz.JobExecutionException; 

public class HelloJob implements Job { 
    public void execute(final JobExecutionContext jobExecutionContext) throws JobExecutionException { 
        System.out.println(System.currentTimeMillis() + " - helloJob 任务执行"); 
    } 
}

第二步:使用 job、trigger、schedule 调用定时任务

import static org.quartz.JobBuilder.newJob; 
import static org.quartz.SimpleScheduleBuilder.simpleSchedule; 
import static org.quartz.TriggerBuilder.newTrigger; 
import org.quartz.JobDetail; 
import org.quartz.Scheduler; 
import org.quartz.SchedulerException; 
import org.quartz.Trigger; 
import org.quartz.impl.StdSchedulerFactory; 

public class QuartzTest { 
    public static void main(String[] args) { 
        try { 
            // 获取一个调度程序的实例 
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); 
            System.out.println(scheduler.getSchedulerName() + " - " + scheduler.getSchedulerInstanceId()); 

            // 定义一个 job,并绑定到 HelloJob.class 
            // 这里并不会马上创建一个 HelloJob 实例,实例创建是在 scheduler 安排任务触发执行时创建的 
            JobDetail job = newJob(HelloJob.class) .withIdentity("job1", "group1") .build(); 
            
            // 声明一个触发器 
            // schedule.start() 方法开始调用的时候执行,每间隔2秒执行一次 
            Trigger trigger = newTrigger() 
                  .withIdentity("trigger1", "group1") 
                  .startNow() 
                  .withSchedule( 
                      simpleSchedule().withIntervalInSeconds(3).repeatForever() 
                  ).build(); 
          
            // 安排执行任务 
            scheduler.scheduleJob(job, trigger); 

            // 启动任务调度程序(内部机制是线程的启动) 
            scheduler.start(); 

            Thread.sleep(30000); 
            
            // 关闭任务调度程序 
            scheduler.shutdown(); 
        } catch (SchedulerException e) { 
            e.printStackTrace(); 
        } catch (InterruptedException e) { 
            e.printStackTrace(); 
        } 
    } 
}

第三步:执行程序

DefaultQuartzScheduler - NON_CLUSTERED 
1560771694931 - helloJob 任务执行 
1560771697879 - helloJob 任务执行 
1560771700876 - helloJob 任务执行 
1560771703880 - helloJob 任务执行 
1560771706876 - helloJob 任务执行 
1560771709881 - helloJob 任务执行 
...

深入理解 Scheduler、Job、Trigger、JobDetail

Scheduler 调度程序,只有安排进执行计划的 Job(通过 scheduler.scheduleJob 方法安排),当生命的执行时间(Trigger)到了的时候,该任务才会执行。

Quartz API 几个重要接口:

Scheduler,用于与调度程序交互的主程序接口。
Job,被调度程序执行的任务类。
JobDetail,使用 JobDetail 来定义定时任务的实例。
Trigger,触发器,表明任务在什么时候会执行。
JobBuilder,用于声明一个任务实例,也可以定义关于该任务的详情比如任务名、组名等。
TriggerBuilder,触发器创建器,用于创建触发器 trigger。

Scheduler 调度程序

org.quartz.Scheduler 是 Quartz 调度程序的主要接口。Scheduler 维护了一个 JobDetails 和 Triggers 的注册表。一旦在 Scheduler 注册过了,当定时任务触发时间一到,调度程序就会负责执行预先定义的 Job。

调度程序创建之后,处于“待机”状态,必须调用 scheduler 的 start() 方法启用调度程序。可以使用 shutdown() 方法关闭调度程序,使用 isShutdown() 方法判断该调度程序是否已经处于关闭状态。通过 Scheduler.scheduleJob(…) 方法将任务纳入调度程序中,当任务触发时间到了的时候,该任务将被执行。

SchedulerFactory 调度程序工厂

调度程序 Scheduler 实例是通过 SchedulerFactory 工厂来创建的。SchedulerFactory 有两个默认的实现类:DirectSchedulerFactory 和 StdSchedulerFactory。

DirectSchedulerFactory 是一个 org.quartz.SchedulerFactory 的单例实现。

示例1:使用 createVolatileScheduler 方法去创建一个不需要写入数据库的调度程序实例:

// 创建一个拥有10个线程的调度程序 
DirectSchedulerFactory.getInstance().createVolatileScheduler(10); 
DirectSchedulerFactory.getInstance().getScheduler().start();

示例2:使用 createScheduler 方法创建

public void createScheduler(String schedulerName, 
        String schedulerInstanceId, 
        ThreadPool threadPool, 
        JobStore jobStore, 
        String rmiRegistryHost, 
        int rmiRegistryPort)

// 创建线程池 
SimpleThreadPool threadPool = new SimpleThreadPool(maxThreads, Thread.NORM_PRIORITY); 
threadPool.initialize(); 

// 创建 JobStore 
JobStore jobStore = new RAMJobStore(); 

// 创建调度程序 
DirectSchedulerFactory.getInstance().createScheduler("My Quartz Scheduler", "My Instance", threadPool, jobStore, "localhost", 1099); 

// 启动调度程序 
DirectSchedulerFactory.getInstance().getScheduler("My Quartz Scheduler", "My Instance").start();

也可使用 JDBCJobStore:

DBConnectionManager.getInstance().addConnectionProvider("someDatasource", 
      new JNDIConnectionProvider("someDatasourceJNDIName")); 
JobStoreTX jdbcJobStore = new JobStoreTX(); 
jdbcJobStore.setDataSource("someDatasource"); 
jdbcJobStore.setPostgresStyleBlobs(true); 
jdbcJobStore.setTablePrefix("QRTZ_"); 
jdbcJobStore.setInstanceId("My Instance");

StdSchedulerFactory

StdSchedulerFactory 是 org.quartz.SchedulerFactory 的实现类,基于 Quartz 属性文件(quartz.properties)创建 Quartz Scheduler 调度程序的。

默认情况下是加载当前工作目录下的 quartz.properties 文件。如果加载失败,会去加载 org/quartz 包下的 quartz.properties 属性文件。可以在 org/quartz 包下找到其默认的属性文件的配置信息:

org.quartz.scheduler.instanceName: DefaultQuartzScheduler 
org.quartz.scheduler.rmi.export: false 
org.quartz.scheduler.rmi.proxy: false 
org.quartz.scheduler.wrapJobExecutionInUserTransaction: false 
org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool 
org.quartz.threadPool.threadCount: 10 
org.quartz.threadPool.threadPriority: 5 
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true 
org.quartz.jobStore.misfireThreshold: 60000 
org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore

如果不想使用默认的文件名,可以指定 org.quartz.properties 属性指向属性配置文件。或者可以在调用 getScheduler() 方法之前调用 initialize(xxx)方法初始化工厂配置。

属性配置文件中,还可以引用其他配置文件的信息,可以使用 $@ 来引用:

quartz1.properties

org.quartz.scheduler.instanceName=HelloScheduler

quartz2.properties

org.quartz.scheduler.instanceName=$@org.quartz.scheduler.instanceName

参照以下 StdSchedulerFactory 的属性配置,实际使用中可以指定一些符合需求的参数。

PROPERTIES_FILE = "org.quartz.properties"; 
PROP_SCHED_INSTANCE_NAME = "org.quartz.scheduler.instanceName"; 
PROP_SCHED_INSTANCE_ID = "org.quartz.scheduler.instanceId"; 
PROP_SCHED_INSTANCE_ID_GENERATOR_CLASS = "org.quartz.scheduler.instanceIdGenerator.class"; 
PROP_SCHED_THREAD_NAME = "org.quartz.scheduler.threadName"; 
PROP_SCHED_SKIP_UPDATE_CHECK = "org.quartz.scheduler.skipUpdateCheck"; 
PROP_SCHED_BATCH_TIME_WINDOW = "org.quartz.scheduler.batchTriggerAcquisitionFireAheadTimeWindow"; 
PROP_SCHED_MAX_BATCH_SIZE = "org.quartz.scheduler.batchTriggerAcquisitionMaxCount"; 
PROP_SCHED_JMX_EXPORT = "org.quartz.scheduler.jmx.export"; 
PROP_SCHED_JMX_OBJECT_NAME = "org.quartz.scheduler.jmx.objectName"; 
PROP_SCHED_JMX_PROXY = "org.quartz.scheduler.jmx.proxy"; 
PROP_SCHED_JMX_PROXY_CLASS = "org.quartz.scheduler.jmx.proxy.class"; 
PROP_SCHED_RMI_EXPORT = "org.quartz.scheduler.rmi.export"; 
PROP_SCHED_RMI_PROXY = "org.quartz.scheduler.rmi.proxy"; 
PROP_SCHED_RMI_HOST = "org.quartz.scheduler.rmi.registryHost"; 
PROP_SCHED_RMI_PORT = "org.quartz.scheduler.rmi.registryPort"; 
PROP_SCHED_RMI_SERVER_PORT = "org.quartz.scheduler.rmi.serverPort"; 
PROP_SCHED_RMI_CREATE_REGISTRY = "org.quartz.scheduler.rmi.createRegistry"; 
PROP_SCHED_RMI_BIND_NAME = "org.quartz.scheduler.rmi.bindName"; 
PROP_SCHED_WRAP_JOB_IN_USER_TX = "org.quartz.scheduler.wrapJobExecutionInUserTransaction"; 
PROP_SCHED_USER_TX_URL = "org.quartz.scheduler.userTransactionURL"; 
PROP_SCHED_IDLE_WAIT_TIME = "org.quartz.scheduler.idleWaitTime"; 
PROP_SCHED_DB_FAILURE_RETRY_INTERVAL = "org.quartz.scheduler.dbFailureRetryInterval"; 
PROP_SCHED_MAKE_SCHEDULER_THREAD_DAEMON = "org.quartz.scheduler.makeSchedulerThreadDaemon"; 
PROP_SCHED_SCHEDULER_THREADS_INHERIT_CONTEXT_CLASS_LOADER_OF_INITIALIZING_THREAD = "org.quartz.scheduler.threadsInheritContextClassLoaderOfInitializer"; 
PROP_SCHED_CLASS_LOAD_HELPER_CLASS = "org.quartz.scheduler.classLoadHelper.class"; 
PROP_SCHED_JOB_FACTORY_CLASS = "org.quartz.scheduler.jobFactory.class"; 
PROP_SCHED_INTERRUPT_JOBS_ON_SHUTDOWN = "org.quartz.scheduler.interruptJobsOnShutdown"; 
PROP_SCHED_INTERRUPT_JOBS_ON_SHUTDOWN_WITH_WAIT = "org.quartz.scheduler.interruptJobsOnShutdownWithWait"; 
PROP_THREAD_POOL_CLASS = "org.quartz.threadPool.class"; 
PROP_JOB_STORE_CLASS = "org.quartz.jobStore.class"; 
PROP_JOB_STORE_USE_PROP = "org.quartz.jobStore.useProperties"; 
PROP_CONNECTION_PROVIDER_CLASS = "connectionProvider.class";

Job 定时任务实例类

任务是一个实现 org.quartz.Job 接口的类,任务类必须含有空构造器,只有一个方法:

void execute(JobExecutionContext context) throws JobExecutionException;

当关联这个任务实例的触发器表明的执行时间到了的时候,调度程序 Scheduler 会调用这个方法来执行任务,任务内容就可以在这个方法中执行。

public class HelloJob implements Job { 
    @Override 
    public void execute(JobExecutionContext context) throws JobExecutionException { 
        System.out.println(System.currentTimeMillis() + " - helloJob 任务执行"); 
    } 
}

在该方法退出之前,会设置一个结果对象到 JobExecutionContext 中。尽管这个结果对 Quartz 来说没什么意义,但是 JobListeners 或者 TriggerListeners 可以监听查看 job 的执行情况。。

JobDataMap 提供了一种“初始化成员属性数据的机制”,在实现该 Job 接口的时候可能会用到。

Job 实例化的过程

首先创建一个 Job 类,在调度程序中可以创建很多个 JobDetai,分别设置不同的 JobDataMap

举个例子说明,创建一个类 SalesReportJob 实现 Job 接口,用做销售报表使用。可以通过 JobDataMap 指定销售员的名称和销售报表的依据等等。这就会创建多个 JobDetails 了,例如 SalesReportForJoeSalesReportForMike 分别对应在 JobDataMap 中指定的名字 joe 和 mike。

当触发器的执行时间到了的时候,会加载与之关联的 JobDetail,并在调度程序 Scheduler 中通过 JobFactory 的配置实例化它引用的 Job。

JobFactory 调用 newInstance() 创建一个任务实例,然后调用 setter 方法设置在 JobDataMap 定义好的名字。可以实现 JobFactory,比如使用 IOC/DI 机制初始化的任务实例。

Job 的声明和并发

以下对注解使用在 Job 实现类中,可以影响 Quartz 的行为:

@DisallowConcurrentExecution

告诉 Quartz 不要执行多个任务实例。

在上面的 SalesReportJob 类添加该注解,将会只有一个 SalesReportForJoe 实例在给定的时间执行,但是 SalesReportForMike 是可以执行的。这个约束是基于 JobDetail 的,而不是基于任务类的。

@PersistJobDataAfterExecution

告诉 Quartz 在任务执行成功完毕之后(没有抛出异常),修改 JobDetail 的 JobDataMap 备份,以供下一个任务使用。

如果使用了 @PersistJobDataAfterExecution 注解,强烈建议同时使用 @DisallowConcurrentExecution 注解,以避免当两个同样的 Job 并发执行的时候产生的存储数据混乱。

Job 的其他属性

  • 持久化,如果一个任务不是持久化的,则当没有触发器关联它的时候,Quartz 会从 scheduler 中删除它。
  • 请求恢复,如果一个任务请求恢复,一般是该任务执行期间发生了系统崩溃或者其他关闭进程的操作,当服务再次启动的时候,会再次执行该任务。这种情况下,JobExecutionContext.isRecovering() 会返回 true。

JobDetail 定义任务实例的一些属性特征

org.quartz.JobDetail 接口负责传输给定的任务实例的属性到 Scheduler。JobDetail 是通过 JobBuilder 创建的。
Quartz 不会存储一个真实的 Job 类实例,但是允许通过 JobDetail 定义一个任务实例。
任务有一个名称 name 和 group 来关联,在一个 Scheduler 中这二者的组合必须是唯一的。
多个触发器可以指向同一个 Job,但一个触发器只能指向一个 Job。

JobDataMap 任务数据映射

JobDataMap 用来保存任务实例的状态信息。当一个 Job 被添加到 scheduler 的时候,JobDataMap 实例就会存储一次关于该任务的状态信息数据。也可以使用 @PersistJobDataAfterExecution 注解标明在一个任务执行完毕之后就存储一次。

JobDataMap 实例也可以存储一个触发器。这是非常有用的,特别是当任务被多个触发器引用的时候,根据不同的触发时机,你可以提供不同的输入条件。

JobExecutionContext 也可以再执行时包含一个 JobDataMap ,合并了触发器的 JobDataMap (如果有的话)和 Job 的 JobDataMap (如果有的话)。

在定义 JobDetail 的时候,将一些数据放入JobDataMap 中:

JobDetail job = newJob(HelloJob.class) 
    .withIdentity("job1", "group1") 
    .usingJobData("jobSays", "Hello World!") 
    .usingJobData("myFloatValue", 3.141f) 
    .build();

在任务执行的时候,可以获取 JobDataMap 中的数据:

@Override 
public void execute(JobExecutionContext context) throws JobExecutionException { 
    JobKey key = context.getJobDetail().getKey(); 
    JobDataMap dataMap = context.getJobDetail().getJobDataMap(); 
    String jobSays = dataMap.getString("jobSays"); 
    float myFloatValue = dataMap.getFloat("myFloatValue"); 
    // ... 
}

在触发器也添加数据:

Trigger trigger = newTrigger() 
    .withIdentity("trigger1", "group1") 
    .usingJobData("trigger_key", "每2秒执行一次") 
    .startNow() 
    .withSchedule(simpleSchedule() 
        .withIntervalInSeconds(2) 
        .repeatForever()) 
    .build();

任务执行方法可以获取到合并后的数据

@Override 
public void execute(JobExecutionContext context) throws JobExecutionException { 
    JobKey key = context.getJobDetail().getKey(); 
    
    // 使用归并的 JobDataMap 
    JobDataMap dataMap = context.getMergedJobDataMap(); 
    String jobSays = dataMap.getString("jobSays"); 
    float myFloatValue = dataMap.getFloat("myFloatValue"); 
    String triggerSays = dataMap.getString("trigger_key"); 
    // ... 
}

Trigger 触发器

触发器使用 TriggerBuilder 来实例化,有一个 TriggerKey 关联,在一个 Scheduler 中必须是唯一的。
多个触发器可以指向同一个工作,但一个触发器只能指向一个工作。
触发器可以传送数据给 job,通过将数据放进触发器的 JobDataMap。

触发器常用属性

触发器也有很多属性,这些属性都是在使用 TriggerBuilder 定义触发器时设置的。

  • TriggerKey,唯一标识,在一个 Scheduler 中必须是唯一的
  • startTime,开始时间,通常使用 startAt(java.util.Date)
  • endTime,结束时间,设置了结束时间则在这之后,不再触发

触发器的优先级

有时候,会安排很多任务,但是 Quartz 并没有更多的资源去处理它。这种情况下,必须需要很好地控制哪个任务先执行。这时候可以设置 priority 属性(使用方法 withPriority(int))来控制触发器的优先级。
优先级只有触发器出发时间一样的时候才有意义。
当一个任务请求恢复执行时,它的优先级和原始优先级是一样的。

JobBuilder 用于创建 JobDetail,TriggerBuilder 用于创建触发器 Trigger

JobBuilder 用于创建 JobDetail,如果没有调用 withIdentity(..) 指定 job 的名字,会自动生成一个。
TriggerBuilder 用于创建 Trigger,如果没有调用 withSchedule(..) 方法,会使用默认的 schedule 。如果没有使用 withIdentity(..) 会自动生成一个触发器名称。

Quartz 通过一种领域特定语言(DSL)提供了一种自己的 builder 的风格API来创建任务调度相关的实体。DSL 可以通过对类的静态方法的使用来调用:TriggerBuilder、JobBuilder、DateBuilder、JobKey、TriggerKey 以及其它的关于 Schedule 创建的实现。

// import static org.quartz.JobBuilder.newJob; 
// import static org.quartz.SimpleScheduleBuilder.simpleSchedule; 
// import static org.quartz.TriggerBuilder.newTrigger; 

JobDetail job = newJob(MyJob.class) 
    .withIdentity("myJob") 
    .build(); 

Trigger trigger = newTrigger() 
    .withIdentity(triggerKey("myTrigger", "myTriggerGroup")) 
    .withSchedule(simpleSchedule() 
        .withIntervalInHours(1) 
        .repeatForever()) 
    .startAt(futureDate(10, MINUTES)) 
    .build(); 

scheduler.scheduleJob(job, trigger);

Cron 表达式的使用

Quartz 提供了多种触发器:

最常用的两种触发器:简单触发器 SimpleTrigger、基于 Cron 表达式的触发器 CronTrigger

简单触发器 SimpleTrigger

SimpleTrigger 是接口 Trigger 的一个具体实现,可以触发一个已经安排进调度程序的任务,并可以指定时间间隔重复执行该任务。

SimpleTrigger 包含几个特点:开始时间、结束时间、重复次数以及重复执行的时间间隔。

重复的次数可以是零,一个正整数,或常量 SimpleTrigger.REPEAT_INDEFINITELY。

重复执行的时间间隔可以是零,或者 long 类型的数值表示毫秒。值得注意的是,零重复间隔会造成触发器同时发生(或接近同时)。

结束时间的会重写重复的次数,这可能是有用的,如果你想创建一个触发器,如每10秒触发一次,直到一个给定的时刻,而不是要计算的次数,它会在开始时间和结束时间重复执行。结束时间一到,就算你指定了重复次数很多次(比如执行10W次),但是时间一到它将不再执行。

SimpleTrigger 实例创建依赖于 TriggerBuilder 和 SimpleScheduleBuilder ,使用 Quartz 提供的DSL风格创建触发器实例,

import static org.quartz.TriggerBuilder.*; 
import static org.quartz.SimpleScheduleBuilder.*; 
import static org.quartz.DateBuilder.*:

可以创建很多不同形式的触发器:

创建一个指定时间开始执行,但是不重复的触发器,使用 startAt(java.util.Date) 设置触发器的第一次执行时间:

SimpleTrigger trigger = (SimpleTrigger) newTrigger() 
    .withIdentity("trigger1", "group1") 
    .startAt(myStartTime) 
    .forJob("job1", "group1") 
    .build();

创建一个指定时间开始执行,每10s执行一次,共执行10次的触发器
使用 SimpleScheduleBuilder.withIntervalInSeconds(N) 方法可以指定间隔N秒就执行一次;withRepeatCount(M) 可以指定执行次数M。

SimpleScheduleBuilder 有很多类似的方法。

Trigger trigger = newTrigger() 
    .withIdentity("trigger3", "group1") 
    .startAt(myTimeToStartFiring) 
    .withSchedule( 
        simpleSchedule() 
            .withIntervalInSeconds(10) 
            .withRepeatCount(10) 
    ) 
    .forJob(myJob) 
    .build();

创建一个在未来第五分钟的时候执行一次的触发器

使用 DateBuilder 的 futureDate 方法可以指定在未来时间执行。

Trigger trigger = (SimpleTrigger) newTrigger() 
    .withIdentity("trigger5", "group1") 
    .startAt(futureDate(5, IntervalUnit.MINUTE)) 
    .forJob(myJobKey) 
    .build();

创建一个马上执行,每隔5分钟执行、直到22:00结束执行的触发器
使用 TriggerBuilder 的 startNow() 方法立即触发(scheduler 调用 start 时算起,视优先级而定);
withIntervalInMinutes(5) 每5分钟执行一次;
repeatForever() 一直重复;
endAt(dateOf(22, 0, 0)) 直到22:00终结触发器:

Trigger trigger = newTrigger() 
    .withIdentity("trigger7", "group1") 
    .startNow() 
    .withSchedule( 
        simpleSchedule() 
            .withIntervalInMinutes(5) 
            .repeatForever() 
    ) 
    .endAt(dateOf(22, 0, 0)) 
    .build();

创建一个在偶数小时执行、每两个小时执行一次的触发器

Trigger trigger = newTrigger() 
    .withIdentity("trigger8") 
    .startAt(evenHourDate(null)) 
    .withSchedule(simpleSchedule().withIntervalInHours(2).repeatForever())     
    .build();

值得注意的是,如果没有调用 startAt(..) 方法,默认使用 startNow()

关于简单触发器“熄火(misfire)”的指令

SimpleTrigger 包含一些指令在“熄火”时可以告知 Quartz 怎么去处理。这些指令包含在 SimpleTrigger 的常量中。

  • REPEAT_INDEFINITELY - 用于表示触发器的“重复计数”是不确定的。或者换句话说,触发应该不断重复直到触发的结尾时间戳

  • MISFIRE_INSTRUCTION_FIRE_NOW - 如果熄火,该指令会告诉 Quartz 应该马上再次触发

  • MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT - 如果熄火,该指令会告诉 Quartz 马上执行并计数累计到已经执行的次数当中去,如果结束时间已经过了,则不会再执行。

  • MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT - 如果熄火,会告诉 Quartz 想要现在就执行一次(即使现在不是它原本计划的触发时间)

  • MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT - 如果熄火,会告诉 Quartz 在下一次执行时间再次开始执行

一个使用“熄火”策略的触发器示例:

Trigger trigger = newTrigger() 
    .withIdentity("trigger7", "group1") 
    .withSchedule(
        simpleSchedule()
            .withIntervalInMinutes(5)
            .repeatForever() 
            .withMisfireHandlingInstructionNextWithExistingCount() 
    ) 
    .build();

基于 Cron 表达式的触发器 CronTrigger

CronTrigger 通常使用得比 SimpleTrigger 多一些。特别是基于日历的概念,而不是对具体间隔的行为。

通过 CronTrigger,可以指定“每个星期五的中午”、“每个工作日上午9:30”,“一月的每星期一的上午9点至10点之间的每5分钟,星期三和星期五” 执行。

Cron 表达式

首先了解 Cron 表达式,是用于配制 CronTrigger 实例的。Cron 表达式,实际上是由七个子表达式组成的字符串,它描述了不同的调度细节。这些子表达式是用空格分隔的,并表示:

秒 
分 
时 
月中的天 
月 
周中的天 
年(可选项)

例如: “0 0 12 ? * WED” 表示 “每个星期三的12点”,单个子表达式可以包含范围和/或列表,例如:

"0 0 7 ? * MON-FRI" 表示 "每个工作日的7点" 
"0 0 19 ? * MON,WED,FRI" 表示 "周一、周三和周五的19点" 
"0 0 14 ? * MON-WED,SAT" 表示 "周一到周三以及周六的14点"

Cron 表达式的规则说明

所有字段都有一组可以指定的有效值。

  • 数字 0 到 59 可以表示秒和分
  • 0到23可以表示小时
  • 月中的天可以使用1到31的数值, 但是你要注意该月的天数!
  • 月用0 到 11之间的数值表示, 或者使用JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV 和 DEC来表示1-12月
  • 一周中的天试用1到7表示 (1 表示 周日) 或者使用 SUN, MON, TUE, WED, THU, FRI 和 SAT

创建 CronTrigger

CronTrigger 实例使用 TriggerBuilder 和 CronScheduleBuilder 创建

创建一个8到17点间每两分钟执行一次的 Cron 触发器:

Trigger cronTrigger1 = newTrigger() 
    .withIdentity("trigger3", "group1") 
    .withSchedule(cronSchedule("0 0/2 8-17 * * ?")) 
    .forJob("myJob", "group1") 
    .build();

创建一个每天10:42执行的Cron触发器:

Trigger cronTrigger2 = newTrigger() 
    .withIdentity("trigger3", "group1") 
    .withSchedule(dailyAtHourAndMinute(10, 42)) 
    .forJob(job.getKey()) 
    .build(); 

Trigger cronTrigger3 = newTrigger() 
    .withIdentity("trigger3", "group1") 
    .withSchedule(cronSchedule("0 42 10 * * ?")) 
    .forJob(job.getKey()) 
    .build();

关于 Cron 触发器“熄火”的指令

CronTrigger 同样包含一些指令在它“熄火”时可以告知 Quartz 怎么去处理。

  • MISFIRE_INSTRUCTION_FIRE_ONCE_NOW - 如果熄火,该指令会告诉 Quartz 希望马上再次触发

  • MISFIRE_INSTRUCTION_DO_NOTHING - 如果熄火,该指令会告诉 Quartz 下一次执行时间到来时再执行,并不想马上执行

Trigger cronTrigger4MisfireInstruction = newTrigger() 
    .withIdentity("trigger3", "group1") 
    .withSchedule(cronSchedule("0 0/2 8-17 * * ?")       
        .withMisfireHandlingInstructionFireAndProceed()) 
    .forJob("myJob", "group1") 
    .build();

常用的 Cron 表达式例子:

0 0 12 * * ? 每天12点执行 
0 15 10 ? * * 每天的10:15执行 
0 15 10 * * ? 每天的10:15执行 
0 15 10 * * ? * 每天的10:15执行 
0 15 10 * * ? 2005 2005年每天的10:15执行 
0 * 14 * * ? 每天的14:00到14:59期间每分钟执行 
0 0/5 14 * * ? 每天的14:00到14:55每隔5分钟执行 
0 0/5 14,18 * * ? 每天的14:00到14:55每隔5分钟执行和18:00到18:55每隔5分钟执行 
0 0-5 14 * * ? 每天的14:00到14:05执行 
0 10,44 14 ? 3 WED 三月的每一个周三的14:10和14:44执行 
0 15 10 ? * MON-FRI 工作日每天的10:15:00执行 
0 15 10 15 * ? 每个月的第15天的10:15:00执行 
0 15 10 L * ? 每个月最后一天的10:15:00执行 
0 15 10 ? * 6L 每个月最后一个周五的10:15:00执行 
0 15 10 ? * 6L 2002-2005 2002, 2003, 2004, 和2005年每个月最后一个周五的10:15:00执行 
0 15 10 ? * 6#3 每个月的第三个周五的10:15:00执行 
0 0 12 1/5 * ? 每个月的第一天的12:00:00开始执行,每隔5天间隔执行 
0 11 11 11 11 ? 每年的11月11日11:11:00执行
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

grails4.0.1配置Quartz定时任务 的相关文章

随机推荐

  • 数据结构与算法-线性表-线性存储结构

    数据结构中的线性表的第一种物理结构 顺序存储结构 它的c 实现代码如下 ifndef SHUNXUBIAO H define SHUNXUBIAO H include
  • js模糊匹配数组中含有某字符串

    数组中匹配单个字符串的方法 传入数组支持格式 function searchStr str arr let newList 要匹配字符串的首个字符 let startChar str charAt 0 要匹配字符串的字符长度 let str
  • php服务器性能计算,CPU主频和内核数量以及服务器性能之间的关系

    CPU主频和内核数量以及服务器性能之间的关系 要点 本文介绍了CPU主频和内核数量以及服务器性能之间的关系 希望对您有用 如果有疑问 可以联系我们 在系统维护工作中曾经被问到CPU内核数量和主频之间的关系的一个问题 当时和服务器厂家磨叽了几
  • 从数据分布的角度提高对抗样本的可迁移性

    1 引言 对抗迁移性攻击一般是先通过代理模型生成对抗样本 然后将该样本迁移到其它黑盒模型中进行攻击 对抗迁移性的根本原因目前仍有待于探究 以前的工作主要从模型的角度探讨原因 例如决策边界 模型架构和模型容量等 在该论文中 作者从数据分布的角
  • 06-----查看Linux内核版本和系统版本的命令

    1 查看Linux内核版本 1 cat proc version 2 uname a 结果 可以看到Linux内核版本和gcc版本等信息 2 查看Linux系统版本的命令 3种方法 1 lsb release a 这个命令适用于所有的Lin
  • 数学建模(2)

    数学建模 2 主成分分析法 主成分分析实际上是一种降维方法 注意 在实际研究中 由于主成分的目的是为了降维 减少变量的个数 故一般选取少量的主成分 不超过5或6个 只要它们能解释变异的70 80 称累积贡献率 就行了 主成分估计采用的方法是
  • iOS中socket通信---基于CocoaAsyncSocket实现

    前言 最近项目中涉及到socket通信这块 所以有幸有时间大概看了一下这一块 目前还在实现阶段 因此现在还不能去些具体的实现过程 现在只大概描述一下这几天看的资料和自己的一点心得吧 等项目实现之后会将具体的实现流程写出来以供大家参考 Soc
  • 给虚拟机换桌面壁纸

    Ubuntu budgie的桌面环境 他们一直不给我换 那我想换一个好看的壁纸 scp指令 我刚开始用的scp指令 不知道为什么一直连不上 所以没有用这个来上传图片文件 scp local file remote username remo
  • usaco Milk Routing

    这个题 自己当时做的时候真脑残 因为每条边上面加了容量c的限制 所以就把我吓的用暴力深搜去搞了 尼玛 数据范围那么 大 好吧 完了 我深搜的过程中加了个烂剪枝 结果还是wa了 真心不知道错在哪了 下来之后看了题解 恶心的题解 我当时也想到枚
  • (java基础学习)异常

    1 基本概念 java语言中 将程序中发生的不正常情况称为 异常 开发过程中的语法错误和逻辑错误不是异常 2 异常事件分为两大类 1 Error 错误 2 Exception 1 Error 错误 java虚拟机无法解决的严重问题 如 JV
  • element ui动画加载

    在正常的业务交互中我们都无法避免接口的交互 这里就会出现一些性能比较差的接口 加载的时间比较长 还有时我们正在加载某个东西时会希望用户别去操作其他东西 确实element给我们封装了一个非常简单好用的加载动画组件 我们只需要在任意元素上绑定
  • wireshark 找不到wifi网卡

    我今天用wireshark想来试一下抓包 我是用wifi上网 结果wireshark上根本就找不到无线网卡 原因是因为我没有打开npf服务 原本我也不知道是因为没有开启npf服务的原因 我偶然中打开了wireshark安装目录下的wires
  • Linux下ffmpeg的基本编译

    Linux下ffmpeg的基本编译 Linux下编译 1 源码下载地址 http ffmpeg org download html 2 将源码包上传到Linux编译服务器上并解压出来 3 创建编译路径 mkdir home compile
  • vue3.0 + element-plus + 上传图片到七牛云

    时间 2021 8 30 想在项目中 把上传的图片存储到 七牛云 上 但是发现 七牛云他只给了一个限时 30 天的 cdn 就是在30天后 这个就会取消掉 不知道上传后的图片还有没有在 需要绑定 备案的域名 就应该可以永久使用了吧 不过我没
  • 华为路由器默认用户名密码

    AR路由器的缺省账号密码如下 V200R003C00版本 AR150 200系列路由器的Telnet缺省用户名为admin 缺省密码为admin 缺省级别为15级 Web网管缺省用户名为admin 缺省密码为admin 缺省级别为15级 其
  • PHP之Base64+php://filter绕过、disabled_function绕过

    目录 一 Base64 php filter绕过 1 思路分析 2 实践验证 二 disabled function绕过 一 Base64 php filter绕过 上课讲了这样一道题 一起来看下 以下代码适用于PHP7 x及以上 5的版本
  • 'react-scripts' 不是内部或外部命令,也不是可运行的程序 或批处理文件--错误处理

    react scripts 不是内部或外部命令 也不是可运行的程序 或批处理文件 错误处理 使用create react app创建项目时 项目运行的好好的 写了一些组件引了一些其他库之后 再次运行项目就报错了 报错信息如下 导致以上错误出
  • Python官方推荐30本面向初学者的书籍!你看过几本?

    现在大多数初学者学习python都是看教学视频 但是小编想说的是 如果你能把一本书籍认认真真的读完 那么比你看教学视频的效果要好的多 今天小编就来带大家看看python官方推荐的30本面向初学者的书籍 总有一本书合你胃口 PYTHON速成班
  • RocksDB写入数据过程DBImpl::Write()源代码分析

    Status DBImpl Write const WriteOptions write options WriteBatch my batch if my batch nullptr return Status Corruption Ba
  • grails4.0.1配置Quartz定时任务

    目录 Job Trigger Schedule 使用 深入理解 Scheduler Job Trigger JobDetail Quartz API 几个重要接口 Scheduler 调度程序 SchedulerFactory 调度程序工厂