Springboot+Quartz(二)--Quartz核心类详解

上一篇写了一个简单的demo演示了Quartz用到了哪些接口和类,这一边就来具体讲讲这些接口和类的含义。
这一篇可以结合下一篇Quartz在项目中的使用结合起来看,这样能更加理解些

对了,这篇文章最实用重要的内容在最下面Cron,不想看理论的可以直接看到最下面

Quartz的核心:Job,Trigger,Scheduler

在这里插入图片描述

  • Job 为作业的接口,为任务调度的对象;
  • JobDetail 作业实例用来描述 Job 的实现类及其他相关的静态信息;
  • Trigger触发器做为作业的定时管理工具,一个 Trigger 只能对应一个作业实例,而一个作业实例可对应多个触发器
  • Scheduler 做为定时任务容器,是 Quartz 最上层的东西,它提携了所有触发器和作业,使它们协调工作,每个 Scheduler 都存有 JobDetail 和 Trigger 的注册,一个 Scheduler 中可以注册多个 JobDetail 和多个 Trigger。
  • Trigger和Job的name、group:
    • name是用来标记的
    • group是用来分组方便管理的

验证一个作业实例被多个触发器调用:

先说下:group组的作用,即定义的SimpleJob

是为了方便操作,可以同时对一组任务进行操作。

如停止一组任务或者触发器:
在这里插入图片描述

修改job2方便打印是哪个触发器触发:
public class SimpleJob2 extends QuartzJobBean {
    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("2 extends QuartzJobBean" + sdf.format(new Date()));
        //打印是哪个触发器触发
        System.out.println("======trigger by:" + jobExecutionContext.getTrigger());
    }
}
修改任务调度器内容,添加一个触发器:
public class TestSimpleJob1 {
    public static void main(String args[]) {
        try {
            // 获取一个调度程序的实例
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
            // 定义一个job,并绑定SimpleJob1、2
            // 这里不回马上创建一个SimpleJob实例,实例创建时在scheduler安排任务出发执行时创建的
            // 设置job的名称及分组
            JobDetail jobDetail1 = JobBuilder.newJob(SimpleJob1.class).withIdentity("SimpleJob1", "SimpleJob").build();
            JobDetail jobDetail2 = JobBuilder.newJob(SimpleJob2.class).withIdentity("SimpleJob2", "SimpleJob").build();

            // 声明触发器
            Trigger trigger1 = TriggerBuilder.newTrigger().withIdentity("SimpleTrigger1", "SimpleJob").startNow()
                    .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever()).build();
            Trigger trigger2 = TriggerBuilder.newTrigger().withIdentity("SimpleTrigger2", "SimpleJob").startNow()
                    .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever()).build();
            Trigger trigger3 = TriggerBuilder.newTrigger().withIdentity("SimpleTrigger3", "SimpleJob")
                    .forJob("SimpleJob2", "SimpleJob").startNow()
                    .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(6).repeatForever()).build();

            scheduler.scheduleJob(jobDetail1, trigger1);
            scheduler.scheduleJob(jobDetail2, trigger2);
            scheduler.scheduleJob(trigger3);

            scheduler.start();
            Thread.sleep(30000);
            scheduler.shutdown();
        } catch (SchedulerException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}
结果:可以看到2个触发器对同一个job执行了
10:56:32.026 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'SimpleJob.SimpleJob1', class=com.yhx.toali.Quartz.QuartzFrame.SimpleDemo.SimpleJob1
10:56:32.031 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
10:56:32.031 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'SimpleJob.SimpleJob2', class=com.yhx.toali.Quartz.QuartzFrame.SimpleDemo.SimpleJob2
10:56:32.044 [DefaultQuartzScheduler_Worker-1] DEBUG org.quartz.core.JobRunShell - Calling execute on job SimpleJob.SimpleJob1
1 implements Job2022-01-17 10:56:32
10:56:32.049 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
10:56:32.049 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'SimpleJob.SimpleJob2', class=com.yhx.toali.Quartz.QuartzFrame.SimpleDemo.SimpleJob2
10:56:32.049 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
10:56:32.050 [DefaultQuartzScheduler_Worker-3] DEBUG org.quartz.core.JobRunShell - Calling execute on job SimpleJob.SimpleJob2
10:56:32.054 [DefaultQuartzScheduler_Worker-2] DEBUG org.quartz.core.JobRunShell - Calling execute on job SimpleJob.SimpleJob2
2 extends QuartzJobBean2022-01-17 10:56:32
2 extends QuartzJobBean2022-01-17 10:56:32
======trigger by:Trigger 'SimpleJob.SimpleTrigger3':  triggerClass: 'org.quartz.impl.triggers.SimpleTriggerImpl calendar: 'null' misfireInstruction: 0 nextFireTime: Mon Jan 17 10:56:38 CST 2022
======trigger by:Trigger 'SimpleJob.SimpleTrigger2':  triggerClass: 'org.quartz.impl.triggers.SimpleTriggerImpl calendar: 'null' misfireInstruction: 0 nextFireTime: Mon Jan 17 10:56:37 CST 2022
10:56:37.015 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'SimpleJob.SimpleJob1', class=com.yhx.toali.Quartz.QuartzFrame.SimpleDemo.SimpleJob1
10:56:37.016 [DefaultQuartzScheduler_Worker-4] DEBUG org.quartz.core.JobRunShell - Calling execute on job SimpleJob.SimpleJob1
10:56:37.016 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
1 implements Job2022-01-17 10:56:37
10:56:37.017 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'SimpleJob.SimpleJob2', class=com.yhx.toali.Quartz.QuartzFrame.SimpleDemo.SimpleJob2
10:56:37.017 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
10:56:37.017 [DefaultQuartzScheduler_Worker-5] DEBUG org.quartz.core.JobRunShell - Calling execute on job SimpleJob.SimpleJob2
2 extends QuartzJobBean2022-01-17 10:56:37
======trigger by:Trigger 'SimpleJob.SimpleTrigger2':  triggerClass: 'org.quartz.impl.triggers.SimpleTriggerImpl calendar: 'null' misfireInstruction: 0 nextFireTime: Mon Jan 17 10:56:42 CST 2022
10:56:38.026 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'SimpleJob.SimpleJob2', class=com.yhx.toali.Quartz.QuartzFrame.SimpleDemo.SimpleJob2
10:56:38.030 [DefaultQuartzScheduler_Worker-6] DEBUG org.quartz.core.JobRunShell - Calling execute on job SimpleJob.SimpleJob2
10:56:38.030 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
2 extends QuartzJobBean2022-01-17 10:56:38
======trigger by:Trigger 'SimpleJob.SimpleTrigger3':  triggerClass: 'org.quartz.impl.triggers.SimpleTriggerImpl calendar: 'null' misfireInstruction: 0 nextFireTime: Mon Jan 17 10:56:44 CST 2022
1

Quartz API 几个重要接口:

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

1.Scheduler调度器:

调度器就相当于一个容器,装载着任务和触发器,该类是一个接口,代表一个 Quartz 的独立运行容器,Trigger 和 JobDetail 可以注册到 Scheduler 中,两者在 Scheduler 中拥有各自的组及名称,组及名称是 Scheduler 查找定位容器中某一对象的依据,Trigger 的组及名称必须唯一,JobDetail 的组和名称也必须唯一(但可以和 Trigger 的组和名称相同,因为它们是不同类型的)。Scheduler 定义了多个接口方法,允许外部通过组及名称访问和控制容器中 Trigger 和 JobDetail。
调度程序创建之后,处于“待机”状态,必须调用 scheduler 的 start() 方法启用调度程序。可以使用 shutdown() 方法关闭调度程序,使用 isShutdown() 方法判断该调度程序是否已经处于关闭状态。通过 Scheduler.scheduleJob(JobDetail,Trigger) 方法将任务z注册到调度程序中,当任务触发时间到了的时候,该任务将被执行。

1.1 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();
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)方法初始化工厂配置。

1.2 SchedulerListener监听器

SchedulerListener 是在 Scheduler 级别的事件产生时得到通知,不管是增加还是移除 Scheduler 中的 Job,或者是 Scheduler 遭遇到了严重的错误时。那些事件多是关于对 Scheduler 管理的,而不是专注于 Job 或 Trigger 的。 org.quartz.SchedulerListener 接口包含了一系列的回调方法,它们会在 Scheduler 的生命周期中有关键事件发生时被调用。
以下是该接口中一些常用方法:(具体的使用可以在下一篇中见到)在这里插入图片描述

2.Job定时任务实例类

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

public interface Job {
    void execute(JobExecutionContext var1) throws JobExecutionException;
}

当关联这个任务实例的触发器表明的执行时间到了的时候,调度程序 Scheduler 会调用这个方法来执行任务,任务内容就可以在这个方法中执行。
JobExecutionContext 类提供了调度应用的一些信息。在该方法退出之前,会设置一个结果对象到 JobExecutionContext 中。尽管这个结果对 Quartz 来说没什么意义,但是 JobListeners 或者 TriggerListeners 可以监听查看 job 的执行情况。。
Job 运行时的信息保存在 JobDataMap 实例中。JobDataMap 提供了一种“初始化成员属性数据的机制”,在实现该 Job 接口的时候可能会用到。

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

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

JobDetail jobDetail1 = JobBuilder.newJob(SimpleJob1.class)
				.withIdentity("SimpleJob1", "SimpleJob").build();

2.2 JobDataMap 任务数据映射(传入变量)

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

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

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

例1:jobDetail中传参

在定义 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"); 
    // ... 
}
例2:trigger中传参

在触发器也添加数据:

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

任务执行方法可以获取到数据

@Override 
public void execute(JobExecutionContext context) throws JobExecutionException { 
    JobKey key = context.getJobDetail().getKey(); 
    
    Trigger trigger = context.getTrigger();
    System.out.println(trigger.getJobDataMap().get("trigger_key"));
}
例3:归并数据

使用归并获取合并后的数据:

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

注意:如果jobDetail和trigger中有相同key的话,会以trigger为主。 即上述jobSays的值是“!!!”

2.3 Job 实例化的过程

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

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

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

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

2.4 Job 的声明和并发

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

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

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

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

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

2.5 JobListener监听器

org.quartz.JobListener 接口包含一系列的方法,它们会由 Job 在其生命周期中产生的某些关键事件时被调用。JobListener 可用的方法:(具体的使用可以在下一篇中见到)

public interface JobListener {
	// 返回一个字符串用以说明 JobListener 的名称。
	//对于注册为全局的监听器,getName() 主要用于记录日志,对于由特定 Job 引用的 JobListener,
	//注册在 JobDetail 上的监听器名称必须匹配从监听器上 getName() 方法的返回值。
	String getName();

	// Scheduler 在 JobDetail 将要被执行时调用这个方法。
    void jobToBeExecuted(JobExecutionContext var1);

	// Scheduler 在 JobDetail 即将被执行,但又被 TriggerListener 否决了时调用这个方法。
    void jobExecutionVetoed(JobExecutionContext var1);

	// Scheduler 在 JobDetail 被执行之后调用这个方法
    void jobWasExecuted(JobExecutionContext var1, JobExecutionException var2);
}

项目中的使用:

2.6 Job 的其他属性

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

3.Trigger 触发器

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

3.1 触发器常用属性

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

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

3.2 触发器的优先级

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

3.3 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);

3.4 TriggerListener监听器

正如 JobListener, org.quartz.TriggerListener 接口也包含一系列给 Scheduler 调用的方法。然而,与 JobListener 有所不同的是, TriggerListener 接口还有关于 Trigger 实例生命周期的方法:(具体的使用可以在下一篇中见到)

public interface TriggerListener {
	// 返回一个字符串用以说明监听器的名称。
	// 对于非全局的 TriggerListener,在 addTriggerListener() 方法中给定的名称必须与监听器的 getName() 方法返回值相匹配。
    String getName();

	// 当与监听器相关联的 Trigger 被触发,Job 上的 execute() 方法将要被执行时,Scheduler 就调用这个方法。
	// 在全局 TriggerListener 情况下,这个方法为所有 Trigger 被调用。
    void triggerFired(Trigger var1, JobExecutionContext var2);

	// 在 Trigger 触发后,Job 将要被执行时由 Scheduler 调用这个方法。
	// TriggerListener 给了一个选择去否决 Job 的执行。
	// 假如这个方法返回 true,这个 Job 将不会为此次 Trigger 触发而得到执行
    boolean vetoJobExecution(Trigger var1, JobExecutionContext var2);

	// Scheduler 调用这个方法是在 Trigger 错过触发时。
	// 如这个方法的 JavaDoc 所指出的,你应该关注此方法中持续时间长的逻辑:
	// 在出现许多错过触发的 Trigger 时,长逻辑会导致骨牌效应。你应当保持这上方法尽量的小。
    void triggerMisfired(Trigger var1);

	// Trigger 被触发并且完成了 Job 的执行时,Scheduler 调用这个方法。
	// 这不是说这个 Trigger 将不再触发了,而仅仅是当前 Trigger 的触发(并且紧接着的 Job 执行) 结束时。
	// 这个 Trigger 也许还要在将来触发多次的。
    void triggerComplete(Trigger var1, JobExecutionContext var2, CompletedExecutionInstruction var3);
}

4.Cron 表达式的使用:

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

4.1 简单触发器 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.*:
4.1.1 可以创建很多不同形式的触发器:
例1:

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

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

创建一个指定时间开始执行,每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();
例3:

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

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

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

创建一个马上执行,每隔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();
例5:

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

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

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

4.1.2 关于简单触发器“熄火(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();

4.2 基于 Cron 表达式的触发器 CronTrigger(重要)

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

通过 CronTrigger,可以指定:

  • 每天凌晨1点执行
  • 每个星期五的中午
  • 每个工作日上午9:30
  • 一月的每星期一的上午9点至10点之间的每5分钟,星期三和星期五
4.2.1 Cron 表达式

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

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

在这里插入图片描述

  • " * “:用于指定所有值。例如,分钟字段中的” * "表示每分钟
  • " ? ":日和星期字段允许使用。它用于指定“无特定值”, 当年需要在两个字段之一中指定某项而不是另一个字段时,这很有用。
    • 例如指定:* * * 15 * * 指定每个月15号,但是你并不知道15号是星期几,所以星期的字段位置不能用*,需要写成:* * * 15 * ?。
  • " - “:表示范围;如小时字段中的"10-12”,表示小时10点到12点执行,即10、11、12的时候执行
  • " , “:用于指定多个值;如小时字段"3,5”,表示3点和5点执行
  • " / “:表示指定增量;如秒字段"0/15”,表示没过15秒执行即:每分钟的0,15,30,45秒执行;但是如果指定秒字段"0/40",表示每过40秒执行,但是一分钟只有60秒,所以这代表只在每分钟的0秒和40秒执行
  • " L ":仅日和星期可用,用在日上表示每月最后一天,星期中使用则表示周六;如果写 0 0 0 ?8 3L * 表示每年的8月第三个周六执行一次;如果写0 0 0 L-3 * ?*表示每年的每月的倒数第三天执行一次;使用L字符时,不要指定列表或者值得范围很重,因为这样会导致混淆/意外结果
  • " W “:仅日中可用,表示指定最接近日期的工作日。例如指定"15W”,如果15号是周日则是16号周一触发,如果15号是周六则是14号周五触发,如果15号本就是工作日,就是当天触发。使用W时,不可使用范围或者列表日;可将LW配合使用表示某月中的最后一个工作日;如果1W是周六,那么只能再当月的周一触发即3号,不能跨月
  • " # ":仅星期中可用,用于指定月份的第n个星期几;例"6 # 3"表示当月的第三个星期中的第六天即当月的第三个星期5;"2 # 1"表示当月的第1个星期,如果"5 # 5"表示当月第5个星期4,如果不存在就不触发;

注意:如果跨天比如出现22-2,晚上10点到凌晨2点,尽量使用多个任务解决

例如: “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点"
4.2.2 创建 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:0014:59期间每分钟执行 
0 0/5 14 * * ? 每天的14:0014:55每隔5分钟执行 
0 0/5 14,18 * * ? 每天的14:0014:55每隔5分钟执行和18:0018:55每隔5分钟执行 
0 0-5 14 * * ? 每天的14:0014:05执行 
0 10,44 14 ? 3 WED 三月的每一个周三的14:1014: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 ? 每年的111111:11:00执行

引用:https://www.jianshu.com/p/e2850153c6f0
https://www.cnblogs.com/wadmwz/p/10315481.html

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
项目描述 在上家公司自己集成的一套系统,用了两个多月的时间完成的:Springboot+Mybatis-plus+ SpringMvc+Shiro+Redis企业级开发系统 Springboot作为容器,使用mybatis作为持久层框架 使用官方推荐的thymeleaf做为模板引擎,shiro作为安全框架,主流技术 几乎零XML,极简配置 两套UI实现(bootstrap+layer ui),可以自由切换 报表后端采用技术: SpringBoot整合SSM(Spring+Mybatis-plus+ SpringMvc),spring security 全注解式的权限管理和JWT方式禁用Session,采用redis存储token及权限信息 报表前端采用B ootstrap框架,结合Jquery Ajax,整合前端Layer.js(提供弹窗)+Bootstrap-table(数据列表展示)+ Bootstrap-Export(各种报表导出SQL,Excel,pdf等)框架,整合Echars,各图表的展示(折线图,饼图,直方图等),使用了layui的弹出层、菜单、文件上传、富文本编辑、日历、选项卡、数据表格等 Oracle关系型数据库以及非关系型数据库(Redis),Oracle 性能调优(PL/SQL语言,SQL查询优化,存储过程等),用Redis做中间缓存,缓存数据 实现异步处理,定时任务,整合Quartz Job以及Spring Task 邮件管理功能, 整合spring-boot-starter-mail发送邮件等, 数据源:druid 用户管理,菜单管理,角色管理,代码生成 运行环境 jdk8+oracle+redis+IntelliJ IDEA+maven 项目技术(必填) Springboot+Mybatis-plus+ SpringMvc+Shiro+Redis 数据库文件 压缩包内 jar包文件 maven搭建 Springboot+Mybatis-plus+ SpringMvc+Shiro+Redis企业级报表后台管理系统 http://localhost:/8080/login admin admin Springboot+Mybatis-plus+ SpringMvc+Shiro+Redis企业级报表后台管理系统Springboot+Mybatis-plus+ SpringMvc+Shiro+Redis企业级报表后台管理系统Springboot+Mybatis-plus+ SpringMvc+Shiro+Redis企业级报表后台管理系统Springboot+Mybatis-plus+ SpringMvc+Shiro+Redis企业级报表后台管理系统Springboot+Mybatis-plus+ SpringMvc+Shiro+Redis企业级报表后台管理系统
项目描述 在上家公司自己集成的一套系统,用了两个多月的时间完成的:Springboot+Mybatis-plus+ SpringMvc+Shiro+Redis企业级开发系统 Springboot作为容器,使用mybatis作为持久层框架 使用官方推荐的thymeleaf做为模板引擎,shiro作为安全框架,主流技术 几乎零XML,极简配置 两套UI实现(bootstrap+layer ui),可以自由切换 报表后端采用技术: SpringBoot整合SSM(Spring+Mybatis-plus+ SpringMvc),spring security 全注解式的权限管理和JWT方式禁用Session,采用redis存储token及权限信息 报表前端采用Bootstrap框架,结合Jquery Ajax,整合前端Layer.js(提供弹窗)+Bootstrap-table(数据列表展示)+ Bootstrap-Export(各种报表导出SQL,Excel,pdf等)框架,整合Echars,各图表的展示(折线图,饼图,直方图等),使用了layui的弹出层、菜单、文件上传、富文本编辑、日历、选项卡、数据表格等 Oracle关系型数据库以及非关系型数据库(Redis),Oracle 性能调优(PL/SQL语言,SQL查询优化,存储过程等),用Redis做中间缓存,缓存数据 实现异步处理,定时任务,整合Quartz Job以及Spring Task 邮件管理功能, 整合spring-boot-starter-mail发送邮件等, 数据源:druid 用户管理,菜单管理,角色管理,代码生成 运行环境 jdk8+oracle+redis+IntelliJ IDEA+maven 项目技术(必填) Springboot+Mybatis-plus+ SpringMvc+Shiro+Redis 数据库文件 压缩包内 jar包文件 maven搭建 Springboot+Mybatis-plus+ SpringMvc+Shiro+Redis企业级报表后台管理系统 http://localhost:/8080/login admin admin Springboot+Mybatis-plus+ SpringMvc+Shiro+Redis企业级报表后台管理系统Springboot+Mybatis-plus+ SpringMvc+Shiro+Redis企业级报表后台管理系统Springboot+Mybatis-plus+ SpringMvc+Shiro+Redis企业级报表后台管理系统Springboot+Mybatis-plus+ SpringMvc+Shiro+Redis企业级报表后台管理系统Springboot+Mybatis-plus+ SpringMvc+Shiro+Redis企业级报表后台管理系统

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值