Quartz 使用:Job、Trigger、Schedule

原文:https://blog.csdn.net/zixiao217/article/details/53044890

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

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 提供了多种触发器:

Trigger

最常用的两种触发器:简单触发器 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执行

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,258评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,335评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,225评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,126评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,140评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,098评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,018评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,857评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,298评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,518评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,400评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,993评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,638评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,661评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容

  • scheduler定时调度系统是大多行业项目都需要的,传统的spring-job模式,个人感觉已经out了,因为存...
    安琪拉_4b7e阅读 2,834评论 4 6
  • Quartz是什么 Quartz是一个开源的作业调度包,能够运行在几乎任何java项目中,小到单机应用,大到电商系...
    零度沸腾_yjz阅读 3,128评论 0 9
  • 在半个月之前,有幸看了xxl-job源码,原本打算写一篇源码分析文章。结果由于琐碎的事情干扰了,搁浅了。本篇文章先...
    cmazxiaoma阅读 15,794评论 3 95
  • Quartz 主要API Scheduler 任务调度器,按照特定的触发规则,自动执行任务 Job 接口,定义需要...
    Impler阅读 1,161评论 0 0
  • 什么是定时任务调度 基于给定的时间点,给定的时间间隔或者给定的执行次数自动完成执行任务 在Java中的定时调度工具...
    Hey_Shaw阅读 2,508评论 2 1