• [Quartz笔记]玩转定时调度


    目录

    简介

    Quartz是什么?

    Quartz是一个特性丰富的、开源的作业调度框架。它可以集成到任何Java应用。

    使用它,你可以非常轻松的实现定时任务的调度执行。

     

    Quartz的应用场景

    场景1:提醒和告警

    场景2:监听事务

    场景3:定时作业

     

    Quartz的安装

    安装

    1.可以直接在官网:http://www.quartz-scheduler.org/ 下载jar包。

    2.如果使用maven,可以在pom.xml中添加以下依赖jar包:

    <dependency>

      <groupId>org.quartz-scheduler</groupId>

      <artifactId>quartz</artifactId>

      <version>2.2.1</version>

    </dependency>

    <dependency>

      <groupId>org.quartz-scheduler</groupId>

      <artifactId>quartz-jobs</artifactId>

      <version>2.2.1</version>

    </dependency>

     

    源码

    Github地址:https://github.com/quartz-scheduler/quartz

     

    Hello World范例

    开始学习之前,惯例还是show一下Hello World。

    例:

    1.先定义一个Job

    import java.util.Date;

     

    import org.quartz.Job;

    import org.quartz.JobExecutionContext;

    import org.quartz.JobExecutionException;

     

    public class HelloJob implements Job {

       @Override

       public void execute(JobExecutionContext context) throws JobExecutionException {

          System.out.println(String.format("Hello World! Time:%s", new Date()));

       }

    }

     

    2.定义Job和Trigger去调度我们定义的HelloJob。

    import org.quartz.JobBuilder;

    import org.quartz.JobDetail;

    import org.quartz.Scheduler;

    import org.quartz.SchedulerFactory;

    import org.quartz.SimpleScheduleBuilder;

    import org.quartz.Trigger;

    import org.quartz.TriggerBuilder;

    import org.quartz.impl.StdSchedulerFactory;

    import org.zp.tent.scheduler.demo.job.HelloJob;

     

    /**

     * @Title HelloQuartz

     * @Description Quartz的Hello World实例

     * @Author zhangpeng

     * @Date 2016年7月6日

     */

    public class HelloWorldDemo {

       public static void main(String[] args) {

          try {

            // 通过schedulerFactory获取一个调度器

            SchedulerFactory schedulerfactory = new StdSchedulerFactory();

     

            // 通过schedulerFactory获取一个调度器

            Scheduler scheduler = schedulerfactory.getScheduler();

     

            // 创建jobDetail实例,绑定Job实现类

            JobDetail jobDetail = JobBuilder.newJob(HelloJob.class).withIdentity("helloJob", "jobGroup1").build();

     

            // 定义调度触发规则,本例中使用SimpleScheduleBuilder创建了一个5s执行一次的触发器

            Trigger trigger = TriggerBuilder.newTrigger().withIdentity("myTrigger", "triggerGroup1").startNow()

                  .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever())

                  .build();

     

            // 把作业和触发器注册到任务调度中

            scheduler.scheduleJob(jobDetail, trigger);

     

            // 启动调度

            scheduler.start();

     

            // 60s后关闭

            Thread.sleep(1000 * 30);

            scheduler.shutdown();

            System.out.println("调度任务结束");

          } catch (Exception e) {

            e.printStackTrace();

          }

       }

    }

    好了,运行一下试试吧。

     

    API

    核心API

    Scheduler接口:

    作用:Scheduler接口是Quartz最核心的接口。Scheduler维护着JobDetailTrigger的注册信息。一旦注册成功,Scheduler负责执行和Job关联的触发器。

    一个Scheduler实例可以视为一个调度作业容器。可以通过startshutdown方法来控制它的生命周期。

    例:

    // 通过schedulerFactory获取一个调度器

    SchedulerFactory schedulerfactory = new StdSchedulerFactory();

     

    // 通过schedulerFactory获取一个调度器

    Scheduler scheduler = schedulerfactory.getScheduler();

     

    // 启动

    scheduler.start();

     

    //关闭

    scheduler.shutdown();

     

    Job接口

    作用:开发者实现该接口定义需要执行的作业。JobExecutionContext类提供调度上下文的各种信息。

    实现Job接口的类还可以使用注解进行修饰。

    @DisallowConcurrentExecution:此注解表示不允许这个Job并发执行

    @PersistJobDataAfterExecution:此注解表示当这个Job的execute方法执行成功后,更新并存储它所持有的JobDetail属性中JobDataMap。如果使用这个注解,强烈建议也使用@DisallowConcurrentExecution,因为并发执行过程中,JobDataMap有可能会发生冲突。

    例:

    public class xxxJob implements Job {

       @Override

       public void execute(JobExecutionContext context) throws JobExecutionException {

          …

       }

    }

     

    JobDetail接口

    作用:用于定义Job实例。

    JobDetail有两个boolean属性。

    isDurable:如果设为false,则对应的Job一旦没有关联的触发器,就会被Scheduler自动删除。

    requestsRecovery:如果设为true,当Job执行中遇到硬中断(例如运行崩溃、机器断电等),Scheduler会重新执行。这种情况下,JobExecutionContext.isRecovering()会返回ture。

     

    JobBuilder类

    作用:用于定义、构建JobDetail实例。

    例:

    // 创建jobDetail实例,绑定Job实现类

    JobDetail jobDetail = JobBuilder.newJob(HelloJob.class).withIdentity("helloJob", "jobGroup1").build();

     

    Trigger接口

    作用:定义Job执行的触发规则。

    Quartz中有多种触发器,最常用的是SimpleTrigger 和 CronTrigger

    SimpleTrigger一般用于只执行一次或在指定时间执行的作业;CronTrigger一般用于周期性执行(例如,每日执行、每周执行)的作业,需要按照指定的时间表达式规则设置调度时间。

     

    Priority:这个属性表示Trigger的权重。当两个Trigger触发时间相同时,权重大的那个先执行。Quartz默认的权重值为5。

     

    Misfire Instruction:在Trigger接口中可以设置错过触发处理机制。就是说在指定触发的时间点由于某种原因错过执行的时机了,这时如何去处理。Quartz提供了多种策略,这里不详述,有兴趣的可以参考官方文档。

     

     

    Job和Trigger的关系

    多个Job可以依赖于一个Trigger;多个Trigger也可以关联一个Job。

    但是,从最佳实践来看,最好让Job和Trigger保持一对多的关系,这样更便于管理。

     

    TriggerBuilder类

    作用:用于定义、构建Trigger实例。

    例:

    下面两种方式是一样的效果,都是创建一个每5s执行一次的触发器

    // 定义调度触发规则, SimpleScheduleBuilder方式

    Trigger trigger = TriggerBuilder.newTrigger().withIdentity("myTrigger","triggerGroup1").startNow().withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever()).build();

     

    // 定义调度触发规则, CronScheduleBuilder方式

    Trigger trigger = TriggerBuilder.newTrigger().withIdentity("myTrigger","triggerGroup1").startNow().withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * * * ?")).build();

    第二种触发器构建方式中使用了形如"0/5 * * * * ?"的CronExpression表达式来创建触发器规则。这里不在细说,在下文的CronExpression表达式一节再详述。

     

    JobDataMap

    JobDetail接口中持有JobDataMap类。开发者可以将作业执行时需要的参数或对象填入这个类中。

    填入数据和获取数据的方式很类似Json。

    例:

    先定义一个Job

    public class WithJobDataMapJob implements Job {

       public void execute(JobExecutionContext context) throws JobExecutionException {

          // 基本信息

          JobKey jobKey = context.getJobDetail().getKey();

          TriggerKey triggerKey = context.getTrigger().getKey();

     

          // 获取JobDataMap的方式:如果是基本类型,JobDataMap提供了多种get方法;如果是引用类型,可以直接get,然后进行强制转换

          JobDataMap dataMap = context.getJobDetail().getJobDataMap();

          Student student = (Student) dataMap.get("student");

          List<String> interests = (List<String>) dataMap.get("interests");

          String word = dataMap.getString("word");

     

          System.out.println(String.format("[JobKey:%s][TriggerKey:%s] of DumbJob print info:", jobKey,triggerKey));

          System.out.println(String.format("[Student]name:%s, age:%d, sex:%s", student.getName(),student.getAge(),

               student.getSex()));

          StringBuilder interestsStr = new StringBuilder();

          for (String item : interests) {

            interestsStr.append(item + " ");

          }

          System.out.println("His interests ars: " + interestsStr.toString());

          System.out.println("He want to say: " + word);

          System.out.println("===================================");

       }

    }

     

    客户端代码:

    public static void main(String[] args) {

          try {

            // 通过schedulerFactory获取一个调度器

            SchedulerFactory schedulerfactory = new StdSchedulerFactory();

     

            // 通过schedulerFactory获取一个调度器

            Scheduler scheduler = schedulerfactory.getScheduler();

     

            // 创建jobDetail实例,绑定Job实现类

            JobDetail jobDetail = JobBuilder.newJob(WithJobDataMapJob.class).withIdentity("myJob","group1").build();

           

            // 使用JobDataMap填入想要携带的特殊信息。可以填入基本数据类型、字符串、集合,甚至是一个对象。填入方式很类似JSON

            Student student = new Student("Jack", 20, "male");

            List<String> interests = new ArrayList<String>();

            interests.add("dancing");

            interests.add("singing");

            interests.add("swimming");

            String word = "Hello World!";

            JobDataMap map = jobDetail.getJobDataMap();

            map.put("student", student);

            map.put("interests", interests);

            map.put("word", word);

     

            // 定义调度触发规则,本例中使用SimpleScheduleBuilder创建了一个5s执行一次的触发器

            Trigger trigger = TriggerBuilder.newTrigger().withIdentity("myTrigger", "triggerGroup1").startNow()

                  .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever())

                  .build();

     

            // 把作业和触发器注册到任务调度中

            scheduler.scheduleJob(jobDetail, trigger);

     

            // 启动调度

            scheduler.start();

     

            // 60s后关闭

            Thread.sleep(1000 * 30);

            scheduler.shutdown();

            System.out.println("调度任务结束");

          } catch (Exception e) {

            e.printStackTrace();

          }

       }

     

    其他常见API

    JobKey 和 TriggerKey

    在Quartz中,可以分别通过JobKey和TriggerKey来唯一地识别一个Job或一个Trigger。

    这两个Key都有两个关键属性:name和group。

     

    CronExpression表达式

    Trigger trigger = TriggerBuilder.newTrigger().withIdentity("myTrigger","triggerGroup1").startNow().withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * * * ?")).build();

    还记得上文中展示的使用CronScheduleBuilder方式构建触发器时的例子吗?在这个例子中,我们使用的表达式字符串"0/5 * * * * ?"是什么意思呢?阅读本节后,你就会了解了。

    表达式规则

    一个cron表达式有至少6个(也可能7个)有空格分隔的时间元素。

    CronTrigger配置完整格式为: [秒] [分] [小时] [日] [月] [周] [年]

    参数设置规则见下表

    字段    

    允许值

    允许的特殊字符

    0-59

    , - * /

    0-59

    , - * /

    小时

    0-23

    , - * /

    日期

    1-31

    , - * ? / L W

    月份

    1-12 或者 JAN-DEC

    , - * /

    星期

    1-7 或者 SUN-SAT

    , - * ? / L #

    年(可选)

    留空, 1970-2099

    , - * /

     cronExpression表达式参数

    符号说明

    通配符*

    表示所有值。

    例如:在分的字段上设置 "*",表示每一分钟都会触发。

    通配符?

    表示不指定值。使用的场景为不需要关心当前设置这个字段的值。

    例如:要在每月的10号触发一个操作,但不关心是周几,所以需要周位置的那个字段设置为"?" 具体设置为 0 0 0 10 * ?

    通配符-

    表示区间。

    例如在小时上设置 "10-12",表示 10,11,12点都会触发。

    通配符,

    表示指定多个值。

    例如在周字段上设置 "MON,WED,FRI" 表示周一,周三和周五触发

    通配符/

    用于递增触发。如在秒上面设置"5/15" 表示从5秒开始,每增15秒触发(5,20,35,50)。在月字段上设置'1/3'所示每月1号开始,每隔三天触发一次。

    通配符L

    表示最后的意思。

    例如在日字段设置上,表示当月的最后一天(依据当前月份,如果是二月还会依据是否是润年[leap]), 在周字段上表示星期六,相当于"7"或"SAT"。如果在"L"前加上数字,则表示该数据的最后一个。例如在周字段上设置"6L"这样的格式,则表示“本月最后一个星期五"

    通配符W

    表示离指定日期的最近那个工作日(周一至周五)。

    例如在日字段上设置"15W",表示离每月15号最近的那个工作日触发。如果15号正好是周六,则找最近的周五(14号)触发, 如果15号是周未,则找最近的下周一(16号)触发。如果15号正好在工作日(周一至周五),则就在该天触发。如果指定格式为 "1W",它则表示每月1号往后最近的工作日触发。如果1号正是周六,则将在3号下周一触发。(注,"W"前只能设置具体的数字,不允许区间"-")。

    小提示:'L'和 'W'可以一组合使用。如果在日字段上设置"LW",则表示在本月的最后一个工作日触发;周字段的设置,若使用英文字母是不区分大小写的,即MON与mon相同。

    通配符#

    表示每月的第几个周几。

    例如在周字段上设置"6#3"表示在每月的第三个周六。注意如果指定"#5",正好第五周没有周六,则不会触发该配置(用在母亲节和父亲节再合适不过了)。

    注:表中月份一行的JAN-DEC,是指一月到十二月的英文缩写;星期一行的SUN-SAT,是指星期天到星期六的英文缩写。

     

    使用表达式的案例

    案例

    意义

    "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 * * ?"

    在每天下午2点到下午2:59期间的每1分钟触发

    "0 0/5 14 * * ?"

    在每天下午2点到下午2:55期间的每5分钟触发

    "0 0/5 14,18 * * ?"

    在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发

    "0 0-5 14 * * ?"

    在每天下午2点到下午2:05期间的每1分钟触发

    "0 10,44 14 ? 3 WED"

    每年三月的星期三的下午2:10和2:44触发

    "0 15 10 ? * MON-FRI"

    周一至周五的上午10:15触发

    "0 15 10 15 * ?"

    每月15日上午10:15触发

    "0 15 10 L * ?"

    每月最后一日的上午10:15触发

    "0 15 10 ? * 6L"

    每月的最后一个星期五上午10:15触发

    "0 15 10 ? * 6L 2002-2005"

    2002年至2005年的每月的最后一个星期五上午10:15触发

    "0 15 10 ? * 6#3"

    每月的第三个星期五上午10:15触发

     

    参考资料

    官方文档:http://www.quartz-scheduler.org/documentation/

    官方2.2版本教程:http://www.quartz-scheduler.org/documentation/quartz-2.2.x/tutorials/

  • 相关阅读:
    JavaScript tips:innerHTML与document.write的差异
    JavaScript tips:window.onload与$(document).ready()的差异
    剑指offer:重建二叉树
    算法:数组去重
    JavaScript tips:Function调用模式对this的影响
    P1217 [USACO1.5]回文质数 Prime Palindromes
    HDU 1002 A + B Problem II
    单片机及其工作原理粗略介绍
    Markdown格式及语法
    Kubernetes入门(三)——使用Deployment运行一个无状态应用
  • 原文地址:https://www.cnblogs.com/aimei/p/12201412.html
Copyright © 2020-2023  润新知