Quartz调度框架

作者: 零度沸腾_yjz | 来源:发表于2017-02-07 12:13 被阅读0次

    Quartz是什么

    Quartz是一个开源的作业调度包,能够运行在几乎任何java项目中,小到单机应用,大到电商系统。Quartz能够创建很容易的调度,也可以创建十个、百个、千个、甚至万个任务的复杂调度。Quartz将任务定义成java组件,能够执行几乎任何你定义的事情。Quartz也支持许多企业级特性,比如JTA和集群。
    Quartz的核心是Scheduler、Job、Trigger。Job负责定义需要执行的任务,Trigger负责调度策略,Scheduler负责将二者组合。

    Quartz官网:http://www.quartz-scheduler.org/
    quartz核心包quartz-2.2.1.jar
    <pre>
    <dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>2.2.1</version>
    </dependency>
    </pre>
    如果想使用quartz内置的job还需要导入quartz-jobs
    <pre>
    <dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz-jobs</artifactId>
    <version>2.2.1</version>
    </dependency>
    </pre>

    简单Demo

    调度的任务,需要实现Job接口,实现execute()方法。execute方法就是需要执行的任务。
    <pre>
    public class MyJob implements Job {

    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        System.out.println("Hello World,Hello Quartz!");
    }
    

    }
    </pre>

    调度流程
    1、通过工厂方法创建调度器(quartz scheduler生命周期的开始)
    2、启动调度器
    3、定义作业
    4、创建触发器
    5、组合任务和触发器
    6、调度结束(quartz scheduler生命周期结束)
    <pre>
    public class HelloQuartz {
    public static void main(String[] args){
    try {
    //通过工厂方法创建调度器,整个程序不会停止,直到调用scheduler.shutdown()
    Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
    //调度启动
    scheduler.start();
    //定义作业,并且绑定我们指定的作业类
    JobDetail job = newJob(MyJob.class).withIdentity("myJob","group1").build();
    //创建触发器
    Trigger trigger = newTrigger().withIdentity("myTrigger","group1").startNow().withSchedule(simpleSchedule().withIntervalInSeconds(20).repeatForever()).build();
    //调度器添加触发器和作业,开始调度
    scheduler.scheduleJob(job,trigger);
    //调度关闭
    // scheduler.shutdown();
    }catch (SchedulerException e){
    e.printStackTrace();
    }
    }
    }
    </pre>
    Quartz的API设计使用的是 DSL(domain specific language),所以非常干净。
    如果你直接运行上面的代码应该会抛出:
    <pre>
    org.quartz.SchedulerConfigException: Thread count must be > 0
    at org.quartz.simpl.SimpleThreadPool.initialize(SimpleThreadPool.java:242)
    at org.quartz.impl.StdSchedulerFactory.instantiate(StdSchedulerFactory.java:1288)
    at org.quartz.impl.StdSchedulerFactory.getScheduler(StdSchedulerFactory.java:1519)
    at org.quartz.impl.StdSchedulerFactory.getDefaultScheduler(StdSchedulerFactory.java:1535)
    at com.yjz.quartz.HelloQuartz.main(HelloQuartz.java:21)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
    </pre>
    提示线程池中初始化的线程要大于0,那么这个线程池数量在哪里配置?原来Quartz使用一个quartz.properties的配置文件,你可以在你项目中classpath创建该文件(web项目在resources下)。quartz的配置文件还是比较好配置的,可以现简单的配置几项:
    <pre>
    org.quartz.scheduler.instanceName = MyScheduler
    org.quartz.threadPool.threadCount = 3
    org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
    </pre>

    quartz核心接口

    Scheduler:与调度器交互的主要API
    Job:任务组件实现的接口
    JobDetail:用来定义任务的实例
    JobBuilder:用于定义JobDetail实例,定义作业实例
    Trigger:为任务定义执行计划的组件
    TriggerBuilder:用来定义Trigger实例
    Scheduler只有调用了start()方法之后才能触发任务执行,quartz的整个生命周期从通过SchedulerFactory创建Scheduler实例到调用shutdown()方法。

    Jobs

    之前说过每个job要实现Job接口,job接口里面中定义了指定:
    <pre>
    public interface Job {
    void execute(JobExecutionContext context)
    throws JobExecutionException;
    }
    </pre>
    当任务触发后,调度器线程会调用excute()方法,JobExecutionContext提供了job运行时的信息,Scheduler、Trigger、JobDetail等都执行它。每次调度器执行job,都会新建job实例,每次job执行完成后,这个实例会被垃圾回收。还有就是这个job必须有一个无参的构造函数(调度器内部要使用)。
    JobDetail:客户端(我们程序)创建JobDetail,并且添加到调度器中,JobDetail包含了job的设置信息。
    JobDataMap是JobDetail的一部分,它可以包含任何数量的数据对象,可以用来显示执行过程。JobDataMap实现java Map接口,提供了一些原始的存储和获取。
    向JobDataMap中添加数据:
    <pre>
    JobDetail job = newJob(MyJob.class).withIdentity("myJob","group1").usingJobData("key1","value1").usingJobData("key2",2).build();
    </pre>
    获取JobMapData:
    <pre>
    public class MyJob implements Job {
    public void execute(JobExecutionContext context) throws JobExecutionException {
    JobKey jobKey = context.getJobDetail().getKey();
    JobDataMap dataMap = context.getJobDetail().getJobDataMap();
    String k1 = dataMap.getString("key1");
    int k2 = dataMap.getInt("key2");
    System.out.println("Hello World,Hello Quartz!");
    }
    }
    </pre>
    JobMapData可以在job中添加一些值,比如:
    <pre>
    public class DumbJob implements Job {
    public DumbJob() {
    }
    public void execute(JobExecutionContext context) throws JobExecutionException {
    JobKey key = context.getJobDetail().getKey();
    JobDataMap dataMap = context.getMergedJobDataMap(); // Note the difference from the previous example
    String jobSays = dataMap.getString("jobSays");
    float myFloatValue = dataMap.getFloat("myFloatValue");
    ArrayList state = (ArrayList)dataMap.get("myStateData");
    state.add(new Date());
    System.err.println("Instance " + key + " of DumbJob says: " + jobSays + ", and val is: " + myFloatValue);
    }
    }
    </pre>
    通过context.getMergedJobDataMap()获取,然后回去对应的state值,然后向state中添加值。或者通过注射,在job类中定义对应的setXxx()实现:
    <pre>
    public class DumbJob implements Job {
    String jobSays;
    float myFloatValue;
    ArrayList state;
    public DumbJob() { }
    public void execute(JobExecutionContext context)throws JobExecutionException{
    JobKey key = context.getJobDetail().getKey();
    JobDataMap dataMap = context.getMergedJobDataMap(); // Note the difference from the previous example
    state.add(new Date());
    System.err.println("Instance " + key + " of DumbJob says: " + jobSays + ", and val is: " + myFloatValue);
    }
    public void setJobSays(String jobSays) {
    this.jobSays = jobSays;
    }
    public void setMyFloatValue(float myFloatValue) {
    myFloatValue = myFloatValue;
    }
    public void setState(ArrayList state) {
    state = state;
    }
    }
    </pre>

    Trigger

    trigger对象用来触发任务执行。当需要调度一个任务时候,需要实例化一个trigger,并且调整参数来给调度器使用。所有的Trigger类型都有TriggerKey属性,用来追踪确定它们的身份。还有一些其它通用参数通过TriggerBuilder构建trigger过程中指定的。
    jobKey:用来表明当trigger触发执行时要执行的任务。
    startTime:用来表明触发器什么时候开始触发执行。这个值是java.util.Date对象定义的时间。
    endTime:用来表明调度触发器什么时候失效。
    trigger有多种触发类型,比较常用的有SimpleTrigger和CronTrigger:

    SimpleTrigger

    当一个任务在一个特定时刻执行一次,或在特定时刻时刻重复执行,可以使用SimpleTrigger。例如你想2017年1月10日 12:32:24执行,或者每十秒执行五次。SimpleTrigger中的开始时间(startTime)、结束时间(endTime)、重复次数(repeat)、重复间隔(repeat interval)。重复可以为0或者一个整数,还可以为SimpleTrigger.REPEAT_INDEFINITELY;重复间隔必须是0或者一个long型,代表毫秒数,如果设置为0将会造成重复触发器同时执行。

    CronTrigger

    如果你需要基于日历的调度,可以使用CronTirgger,CronTrigger比SimpleTrigger更常用。例如每个月的星期五、每个工作日的九点、每周一九点到十点的每五分钟。
    CronTrigger使用Cron-Expressions配置,Cron-Expressions使用以下七种表达式(Sub-Expression)表示:
    1、Seconds
    2、Minutes
    3、Hours
    4、Day-of-Month
    5、Month
    6、Day-of-Week
    7、Year
    例如:”0 0 12 ? * WED”代表每周三的12:00:00pm
    每个二级表达式可以是一个列表,例如:”MON,WED,FRI”
    表达式介绍:

    • 通配符“xxx”代表的是“每次xxxx时候”,“MON-WED,SAT”每周一到周三和周六。
    • ”*”:代表0次或者多次,在Day-Of-Week中代表着”一周中的每一天”。每个字符都需要有效,比如分钟需要在059,小时需要是023,Day-Of-Month必须是131(注意月份的天数),月份是011或者使用:JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV and DEC,Day-Of-Week是1~7(1表示周日)或者是SUN, MON, TUE, WED, THU, FRI and SAT。
    • ”/“:代表值的增量,例如0/15在分钟里(还可以用0,15,30,45) ,代表的是从0,每小时的15分钟。/35在分钟表达式中代表是每小时的第35分钟,从0开始(还可以表示为0,35)。
    • “?”:用在day-of-month和day-of-week里面,用来表示不确定值。
    • “L”:用在day-of-month和day-of-week里面,在day-of-month代表每个月的最后一天比如1月的31号和4四月的30号。在day-of-week中代表每周最后一天7(周六)。也可以用在day-of-week后一个字段值,意识是每个月的最后第几天,例如6L,每个月的最后一个星期五,也可以指定偏移量L-3代表每个月的最后第三天。L不能使用指定的列表中,否则会引起混乱。
    • “W”:代表的意思是工作日,例如15W,这个月的第15个工作日。
    • “#”:代表每个月xxx工作日,例如在day-of-week中6#3,每个月的第三个周五。

    例子:
    “0 0/5 * * *?” 每五分钟
    “10 0/5 * * *?”每五分钟10秒之后
    “0 30 10-13 ? * WED,FRI”代表周三周五的10:30,11:30,12:30,13:30
    “0 0/30 8-9 5,20 * ?”代表的是每个月5号和20的8点到9点的每半个小时。
    代码中使用:
    <pre>
    Trigger trigger1 = newTrigger().withIdentity("myTrigger","group1").withSchedule(cronSchedule("0 0/2 8-17 * * ?")).forJob("myJob","group1").build();
    </pre>
    Quartz将任务和触发分离,这样有很多好处,比如:任务创建和存储与触发器相互独立;许多触发器可以触发同一个任务;还可以任务不变的情况下修改和重新定义触发器,这样有效的对调度器进行了解耦合。
    Jobs和Trigger还定义了key,注册到调度器中,这样允许将这些key放到不同的分组中,这个功能很实用于一个公司内多个部门,每个部门使用自己的组,这个key是名称和分组的组合,并且这个key唯一。
    ScheduleBuilder有多种类型

    Quartz的一些主要特性就是这些,当然还有配置和API可以以后再说。

    相关文章

      网友评论

        本文标题:Quartz调度框架

        本文链接:https://www.haomeiwen.com/subject/naipittx.html