Quartz简单学习

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/belovehejian/article/details/85320676

Quartz是什么,可以做什么

Quartz是一个任务调度框架.Quartz可以在某个固定的时间点进行一些提前设置好需要做的事情.比如,在每天的12点提醒下班,每天凌晨0点自动发一封邮件等.

Quartz涉及到几个要素:

  • Scheduler:调度器,所有的定时任务都是 由它进行调度的
  • Job:具体执行的逻辑(也就是说,需要这个Quartz做什么事情是从这个Job定义的)
  • Trigger:触发器(简单来说就是定义Job被执行的时间,具体什么时间什么方式执行这个Job,由触发器来设置,比如每隔1秒执行一次,或者每天的12点执行一次等).SimpleTrigger和CronTrigger 是最常用的两种.Trigger实例
    • SimpleTrigger
      • SimpleTrigger主要用于一次性执行的Job(只在某个特定的时间点执行一次),或者Job在特定的时间点执行,重复执行N次,每次执行间隔T个时间单位
    • CronTrigger
      • CronTrigger在基于日历的调度上非常有用,如“每个星期五的正午”,或者“每月的第十天的上午10:15”等。
  • JobDetail:定义一些任务数据,Job是具体的任务,而JobDetail则是定义一些其他的任务执行时的任务数据.比如要指定哪个Job实例.这个实例叫什么名字,属于哪个分组等等

简单的示例:

package com.cn.hj.quartz.one;

import com.mchange.v1.db.sql.schemarep.SimpleSchemaRep;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

import static org.quartz.JobBuilder.*;
import static org.quartz.SimpleScheduleBuilder.*;
import static org.quartz.CronScheduleBuilder.*;
import static org.quartz.CalendarIntervalScheduleBuilder.*;
import static org.quartz.TriggerBuilder.*;
import static org.quartz.DateBuilder.*;

public class FirstUsingQuartz {
    public static void main(String[] args) throws SchedulerException {
        SchedulerFactory factory = new StdSchedulerFactory();   //构建定时任务工厂
        Scheduler scheduler = factory.getScheduler(); //获取定时任务实例.
        scheduler.start();  //启动
        JobDetail job = newJob(MyJob.class).withIdentity("myJob", "group1").build();  //定义需要执行的实例,设置工作名称
                                                                                        //同一个分组下的Job实例名称必须要唯一
        Trigger trigger = newTrigger().withIdentity("myTrigger", "group1")  //定义触发器.名称,分组.同一个分组下的Trigger实例名称必须要唯一
                .startNow()
                .withSchedule(simpleSchedule()
                        .withIntervalInSeconds(5)
                        .repeatForever()).build();
        scheduler.scheduleJob(job, trigger);
    }
}

示例采用的是导入静态包的方式编写的.

JobDetail:是利用 JobBuilder 进行构建的.JobBuilder 用于定义/构建JobDetail实例,用于定义作业的实例

Trigger: 是利用TriggerBuilder 进行构建的.TriggerBuilder 用于定义/构建触发器实例

在使用Scheduler之前,需要实例化.可以利用SchedulerFactory进行.scheduler实例化后,可以启动(start)、暂停(stand-by)、停止(shutdown)。注意:scheduler被停止后,除非重新实例化,否则不能重新启动;只有当scheduler启动后,即使处于暂停状态也不行,trigger才会被触发(job才会被执行)。这种执行策略带来的一个后果是,job必须有一个无参的构造函数(当使用默认的JobFactory时);另一个后果是,在job类中,不应该定义有状态的数据属性,因为在job的多次执行中,这些属性的值不会保留。

那么如何给job实例增加属性或配置呢?如何在job的多次执行中,跟踪job的状态呢?答案就是:JobDataMap,JobDetail对象的一部分。

JobDataMap

JobDataMap中可以包含不限量的(序列化的)数据对象,在job实例执行的时候,可以使用其中的数据;JobDataMap是Java Map接口的一个实现,额外增加了一些便于存取基本类型的数据的方法。如果你使用的是持久化的存储机制(本教程的JobStore部分会讲到),在决定JobDataMap中存放什么数据的时候需要小心,因为JobDataMap中存储的对象都会被序列化,因此很可能会导致类的版本不一致的问题

package com.cn.hj.quartz.one;

import com.mchange.v1.db.sql.schemarep.SimpleSchemaRep;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

import static org.quartz.JobBuilder.*;
import static org.quartz.SimpleScheduleBuilder.*;
import static org.quartz.CronScheduleBuilder.*;
import static org.quartz.CalendarIntervalScheduleBuilder.*;
import static org.quartz.TriggerBuilder.*;
import static org.quartz.DateBuilder.*;

public class FirstUsingQuartz {
    public static void main(String[] args) throws SchedulerException {
        SchedulerFactory factory = new StdSchedulerFactory();   //构建定时任务工厂
        Scheduler scheduler = factory.getScheduler(); //获取定时任务实例.
        scheduler.start();  //启动
        //构造JobDataMap,并且添加数据
        JobDataMap jobDataMap = new JobDataMap();
        jobDataMap.put("jobSays", "my name is shensha");
        jobDataMap.put("firstName", "hejian");
        JobDetail job = newJob(MyJob.class)
                .withIdentity("myJob", "group1")     //定义需要执行的实例,设置工作名称.同一个分组下的Job实例名称必须要唯一
                .usingJobData(jobDataMap)   //加入数据
                .build();
        Trigger trigger = newTrigger().withIdentity("myTrigger", "group1")  //定义触发器.名称,分组.同一个分组下的Trigger实例名称必须要唯一
                .startNow()
                .withSchedule(simpleSchedule()
                        .withIntervalInSeconds(5)
                        .repeatForever()).build();
        scheduler.scheduleJob(job, trigger);
    }
}

MyJob:

package com.cn.hj.quartz.one;

import org.quartz.*;

public class MyJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobKey key = context.getJobDetail().getKey();
        JobDataMap dataMap = context.getJobDetail().getJobDataMap();
        String jobSays = dataMap.getString("jobSays");
        String firstName = dataMap.getString("firstName");
        System.out.println("Instance " + key + " of DumbJob says: " + jobSays + ", and val is: " + firstName);
    }
}

Trigger

除了TriggerKey可以表明Trigger的身份之外,Trigger还有一些公共属性

Trigger公共属性

  • jobKey属性:当trigger触发时被执行的job的身份;
  • startTime属性:设置trigger第一次触发的时间;该属性的值是java.util.Date类型,表示某个指定的时间点;有些类型的trigger,会在设置的startTime时立即触发,有些类型的trigger,表示其触发是在startTime之后开始生效。比如,现在是1月份,你设置了一个trigger–“在每个月的第5天执行”,然后你将startTime属性设置为4月1号,则该trigger第一次触发会是在几个月以后了(即4月5号)。
  • endTime属性:表示trigger失效的时间点。比如,”每月第5天执行”的trigger,如果其endTime是7月1号,则其最后一次执行时间是6月5号。

优先级

如果Trigger很多,或者Quartz的工作线程太少,Quartz可能没有足够的资源同时触发所有的trigger.这种情况下,你可能希望控制哪些trigger优先使用Quartz的工作线程,要达到该目的,可以在trigger上设置priority属性.如果没有设置优先级,那么trigger则会使用默认的优先级,默认的优先级是5.priority属性的值可以是任意整数,正数、负数都可以..

只有同时触发的trigger才有优先级.而且10:59的trigger永远要比11:00的trigger先执行,即使11:00的trigger优先级比10:59的要高.

如果trigger是可以恢复的(出现意外之后重新恢复),那么优先级与恢复之前是保持一致的.

 

错过触发

trigger还有一个重要的属性misfire;如果scheduler关闭了,或者Quartz线程池中没有可用的线程来执行job,此时持久性的trigger就会错过(miss)其触发时间,即错过触发(misfire)。不同类型的trigger,有不同的misfire机制。它们默认都使用“智能机制(smart policy)”,即根据trigger的类型和配置动态调整行为。当scheduler启动的时候,查询所有错过触发(misfire)的持久性trigger。然后根据它们各自的misfire机制更新trigger的信息

所有触发器类型的默认值 MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY

SimpleTrigger策略:

MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY

MISFIRE_INSTRUCTION_FIRE_NOW

MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT

策略具体的含义需要查看API

CornTrigger策略

MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY

MISFIRE_INSTRUCTION_DO_NOTHING

MISFIRE_INSTRUCTION_FIRE_NOW

策略具体含义可查看API

排除时间(在某个或者某段时间不执行)

Quartz的Calendar对象(不是java.util.Calendar对象)可以在定义和存储trigger的时候与trigger进行关联.可以利用这个对象排除一些不需要执行此调度任务的时间,比如在每天的12:00 到14:00 不用发送邮件等等.

所有任何实现了Calendar接口的可序列化对象都可以作为Calendar对象

简单示例:

package com.cn.hj.quartz.one;

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.calendar.DailyCalendar;

public class SecondQuartz {
    public static void main(String[] args) throws SchedulerException {
        SchedulerFactory factory = new StdSchedulerFactory();
        Scheduler scheduler = factory.getScheduler();
        scheduler.start();

        DailyCalendar calendar = new DailyCalendar("15:56:00", "15:57:00"); //构造一个时间范围
        scheduler.addCalendar("nodate", calendar, false, false);    //将时间范围添加到调度器当中

        JobDetail jobDetail = JobBuilder.newJob(MyJob.class).withIdentity("myjob2", "group2").build();
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("myTrigger2")
                .startNow()
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).repeatForever())
                .modifiedByCalendar("nodate")   //设置需要排除的指定日期的名称
                .build();
        scheduler.scheduleJob(jobDetail, trigger);

    }
}

示例中排除了每天的 15:56:00 至 15:57:00 这个时间段之内不会执行调度任务.Quartz为我们默认实现了很多个Calendar接口的视实现类,具体可查看相关API.一个Calendar接口对象可以用作多个Trigger触发器.

SimpleTrigger

SimpleTrigger可以满足的调度需求是:在具体的时间点执行一次,或者在具体的时间点执行,并且以指定的间隔重复执行若干次。比如,你有一个trigger,你可以设置它在2019年1月1日的上午00:00:00准时触发,或者在这个时间点触发,并且每隔2秒触发一次,一共重复5次。根据描述,你可能已经发现了,SimpleTrigger的属性包括:开始时间、结束时间、重复次数以及重复的间隔

重复次数,可以是0、正整数,以及常量SimpleTrigger.REPEAT_INDEFINITELY。重复的间隔,必须是0,或者long型的正数,表示毫秒。注意,如果重复间隔为0,trigger将会以重复次数并发执行(或者以scheduler可以处理的近似并发数)。

endTime属性的值会覆盖设置重复次数的属性值;比如,你可以创建一个trigger,在终止时间之前每隔10秒执行一次,你不需要去计算在开始时间和终止时间之间的重复次数,只需要设置终止时间并将重复次数设置为REPEAT_INDEFINITELY(当然,你也可以将重复次数设置为一个很大的值,并保证该值比trigger在终止时间之前实际触发的次数要大即可)。

示例:

指定是开始触发,不重复.

SimpleTrigger trigger = (SimpleTrigger) newTrigger() 
        .withIdentity("trigger1", "group1")
        .startAt(myStartTime)                     // 设置时间
        .forJob("job1", "group1")                 // 标识名称,分组
        .build();

 

指定时间触发,每隔去10秒执行一次,重复10次

trigger = newTrigger()
        .withIdentity("trigger3", "group1")
        .startAt(myTimeToStartFiring)  // 设置触发时间,如果不设置,或者为空,那么表示现在
        .withSchedule(simpleSchedule()
            .withIntervalInSeconds(10)
            .withRepeatCount(10)) // 设置重复10次,实际执行11次, 程序运行的那一刻,会执行一次
        .forJob(myJob) // 标识名称
        .build();

 

5分钟以后触发,仅执行一次

trigger = (SimpleTrigger) newTrigger() 
        .withIdentity("trigger5", "group1")
        .startAt(futureDate(5, IntervalUnit.MINUTE)) // 使用DateBuilder在将来创建日期
        .forJob(myJobKey) // 表示名称
        .build();

立即触发,每5分钟执行一次,直到22:00

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

建立一个触发器,将在下一个小时的整点触发,然后每2小时重复一次:

trigger = newTrigger()
        .withIdentity("trigger8") // 没有指定分组,将使用默认分组
        .startAt(evenHourDate(null)) // 得到下一个偶数小时(分和秒0(“00:00”))
        .withSchedule(simpleSchedule()
            .withIntervalInHours(2)
            .repeatForever())
        .build();

    scheduler.scheduleJob(trigger, job);

CronTrigger

CronTrigger通常比Simple Trigger更有用,如果您需要基于日历的概念而不是按照SimpleTrigger的精确指定间隔进行重新启动的作业启动计划。使用CronTrigger,您可以指定号时间表,例如“每周五中午”或“每个工作日和上午9:30”,甚至“每周一至周五上午9:00至10点之间每5分钟”和1月份的星期五“。即使如此,和SimpleTrigger一样,CronTrigger有一个startTime,它指定何时生效,以及一个(可选的)endTime,用于指定何时停止计划。

Corn表达式相当灵活,Corn的表达式相关可以查询资料学习.

每隔一分钟,每天上午8点至下午5点之间

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

建立一个触发器,将在上午10:42每天发射

trigger = newTrigger()
    .withIdentity("trigger3", "group1")
    .withSchedule(dailyAtHourAndMinute(10, 42))
    .forJob(myJobKey)
    .build();

或者

trigger = newTrigger()
    .withIdentity("trigger3", "group1")
    .withSchedule(cronSchedule("0 42 10 * * ?"))
    .forJob(myJobKey)
    .build();

scheduler生命周期

Scheduler的生命期,从SchedulerFactory创建它时开始,到Scheduler调用shutdown()方法时结束;Scheduler被创建后,可以增加、删除和列举Job和Trigger,以及执行其它与调度相关的操作(如暂停Trigger)。但是,Scheduler只有在调用start()方法后,才会真正地触发trigger(即执行job)

在示例中,我们在创建JobDetail的时候,传入了一个Job实例(实现了Job接口的实例),这样的话Scheduler就知道了要执行何种类型的Job.Scheduler在执行execute()方法之前会创建该类的一个新的实例.执行完毕,对该实例的引用就被丢弃了,实例会被垃圾回收

文章参考w3c中文Quartz教程,以及官方文档.

猜你喜欢

转载自blog.csdn.net/belovehejian/article/details/85320676