一、Quartz是什么

大部分公司都会用到定时任务这个功能。 拿火车票购票来说,当你下单后,后台就会插入一条待支付的task(job),一般是30分钟,超过30min后就会执行这个job,去判断你是否支付,未支付就会取消此次订单;当你支付完成之后,后台拿到支付回调后就会再插入一条待消费的task(job),Job触发日期为火车票上的出发日期,超过这个时间就会执行这个job,判断是否使用等。
在我们实际的项目中,当Job过多的时候,肯定不能人工去操作,这时候就需要一个任务调度框架,帮我们自动去执行这些程序。那么该如何实现这个功能呢?

  • Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用。
  • 在企业级应用中,经常会制定一些“计划任务”,即在某个时间点做某件事情,核心是以时间为关注点,即在一个特定的时间点,系统执行指定的一个操作
  • 任务调度涉及多线程并发、线程池维护、运行时间规则解析、运行现场的保护以恢复等方面
  • Quartz框架是一个开源的企业级任务调度服务,已经被作为任务调度的良好解决方案
  • Quartz对任务调度进行了高度抽象,提出了3个核心概念,并在org.quartz包中通过接口和类进行了描述。

    二、Quartz能干什么?

    场景 #1: 下载交易流水
    每天晚上12整从银行系统自动下载当日交易流水,并进行备份!
    场景 #2: 邮件提醒和告警
    公司出于安全考虑,让每个员工三个月换一次邮箱密码。这种情况下,可以创建一个作业,让它每天午夜运行一次,并且向离过期时间不到三天的所有用户发邮件提醒。这里可以恰到好处的用到作业调度器。图 1.1 描绘了密码这个提醒作业。
    image.png
    图 1.1 密码过期的作业每晚发送邮件给密码很快会过期的用户
    场景 #3 会员续费:
    会员到期的前一周,发送短信提示会员,进行续费!腾讯云服务器,提前一周发送短信,提示续费,如果不续费那么服务器上资源(默认保存一周),如果还不续费直接收回服务器!

    三、Quartz核心概念

  • 任务(JOB):就是执行的工作内容。Quartz提供Job接口来支持任务定义

  • 触发器(Trigger)(何时触发的规则):定义触发Job执行的时间触发规则。Quartz提 供Trigger类及其子类支持触发器功能
  • 调度器(Scheduler):Quartz提供了Scheduler接口,将工作任务和触发器绑定,保证任务可以在正确的时间执行

image.png

四、定时任务入门案例

  1. /**
  2. * @Author HanGuangKai
  3. * @Date 2021/7/25 16:27
  4. * @description 要执行的定时的任务类
  5. */
  6. public class HelloJob implements Job {
  7. /**
  8. * 定时任务要执行的内容
  9. * 一旦触发定时操作,那么这个任务自动执行
  10. * @param jobExecutionContext
  11. * @throws JobExecutionException
  12. */
  13. public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
  14. SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
  15. String nowTime = format.format(new Date());
  16. System.out.println("定时任务被触发了"+nowTime);
  17. }
  18. }
  1. @Test
  2. public void testHello() throws InterruptedException, SchedulerException {
  3. // 1、创建一个任务调度器工厂
  4. SchedulerFactory schedulerFactory = new StdSchedulerFactory();
  5. // 2、根据工厂得到一个调度器
  6. Scheduler scheduler = schedulerFactory.getScheduler();
  7. // 3、创建JobDetail实例,并把任务详情对象和我们自己定义的任务绑定
  8. JobDetail jobDetail = JobBuilder.newJob(HelloJob.class).withIdentity("job1", "group1").build(); // withIdentity():给任务分组
  9. // 4、构建Trigger(触发器)实例,每隔1s执行一次
  10. Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "triggerGroup1").startNow() // 立即生效
  11. // 创建一个 触发器规则——简单触发器
  12. .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1) // 每隔1s执行一次
  13. .repeatForever()).build(); // 一直执行
  14. // 5、进行调度
  15. scheduler.scheduleJob(jobDetail, trigger);
  16. System.out.println("--------scheduler start ! ----------- ");
  17. // 6、执行
  18. scheduler.start();
  19. // 睡眠
  20. TimeUnit.MINUTES.sleep(1);
  21. scheduler.shutdown();
  22. System.out.println("--------scheduler shutdown ! ------------");
  23. }

通过代码可以看到几个重要的类:

  • JobDetail: 真正的任务内容,任务本身是集成Job接口的,但是真正的任务是JobBuilder通过反射的方式实例化的,
  • Trigger: 触发器,定义任务应当开始的时间,主要分为两类SimpleTrigger,CronTrigger,当前例子的就是简单触发器,CronTrigger主要用于处理quartz表达式定义的任务,比如每个月20号,每个星期一之类的。
  • Scheduler: 计划执行者,现在我们有了要做的内容(HelloJob),有了要做的时间(下一分钟),接下来,就把这两个内容填充到计划任务Scheduler对象里面,到了时间它就可以自动运行了.

    五、Quartz架构

    当要深入研究一个技术时,研究它的体系结构和内部运行原理,不失为一种较好的方式。同理,我们在研究Quartz时,也采用类似的方法,下图为Quartz的大致结构图。
    image.png

    Quartz关键组件

    Quartz比较关键的两个核心组件分别为Job和Trigger job—表示任务是什么 trigger—表示何时触发任务。
    image.png

    六、关于Quartz中上下文对象

  • JobDataMap JobDataMap实现了JDK的Map接口,可以以Key-Value的形式存储数据。

  • JobDetail、Trigger都可以使用JobDataMap来设置一些参数或信息, Job执行execute()方法的时候,JobExecutionContext可以获取到JobExecutionContext中的信息
  • useingJobData()可以将job执行时需要的一些参数传递过去

    七、Trigger、SimpleTrigger、**CronTrigger **

    image.png
    Trigger Trigger是Quartz的触发器,会去通知Scheduler何时去执行对应Job。

    1. new Trigger().startAt():表示触发器首次被触发的时间;
    2. new Trigger().endAt():表示触发器结束触发的时间;

    CronTrigger也是Trigger的子类
    CronTrigger功能非常强大,是基于日历的作业调度,而SimpleTrigger是精准指定间隔,所以相比SimpleTrigger,CroTrigger更加常用。CroTrigger是基于Cron表达式的,先了解下Cron表达式: 由7个子表达式组成字符串的。

    1、CronTrigger和SimpleTrigger的对比

    image.png
    CronTrigger允许用户更精准地控制任务的运行日期和时间,而不仅仅是定义工作的频度 CronTrigger通过Cron表达式定义准确的运行时间点。创建CronTrigger的语法如下: 创建CronTrigger的语法很简单,最关键的是Cron表达式的编写要使用CronTrigger,必须掌握Cron表达式
    image.png
    Cron表达式由6~7个由空格分隔的时间元素组成。第7个元素可选:
    image.png

    2、 cron表达式通用符号,-*/

  • , :表示列出枚举值。例如,在Minutes使用5,20,表示在时间的分钟数为5、20时触发事件。

  • -:表示范围。例如,在Minutes域使用5-20,表示在时间的分钟数为5到20每分钟都触发事件。
  • :表示匹配该域的任意值。加入在Minutes域使用,表示时间分钟数不做限制,每分钟都触发事件。
  • /:表示起始时间开始触发,然后每隔固定时间触发一次。例如在Minutes域使用5/20,表示时间的分钟数为5时触发一次,后每隔20分钟,即25,45再分别触发一次。

    3、 cron表达式专有符号

  • ?:表示不作限定(即不指定值)。只能用在DayofMonth和DayofWeek两个域,由于DayofMonth和DayofWeek互斥,须对其一设置。

  • L:表示最后,只能出现在DayofMonth和DayofWeek域,如果对DayofWeek域使用5L,意味着最后的一个星期四触发。
  • W:表示有效工作日(周一到周五),只能出现在DayofMonth域,系统将在离指定日期的最近的有效工作日触发事件。
  • LW:这两个字符可以连用,表示在某个月最后一个工作日
  • :用于确定每个月第几个星期几,只能出现在DayofWeek域。例如在4#2,表示某月的第二个星期三。

  • C:只能用在DayofMonth和DayofWeek域,需要关联日期,如果没关联可以忽略。

    4、案例

    image.png

    5、代码案例

    1. public class HelloJob implements Job {
    2. /**
    3. * 定时任务要执行的内容
    4. * 一旦触发定时操作,那么这个任务自动执行
    5. * @param jobExecutionContext 任务调度的上下文对象:封装正在执行的这个任务的相关运行信息
    6. * @throws JobExecutionException
    7. */
    8. public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
    9. SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
    10. String nowTime = format.format(new Date());
    11. System.out.println("定时任务被触发了"+nowTime);
    12. }
    13. }
    1. @Test
    2. public void test1() throws SchedulerException, InterruptedException {
    3. // 1、创建一个任务调度器工厂
    4. SchedulerFactory schedulerFactory = new StdSchedulerFactory();
    5. // 2、根据工厂得到一个调度器
    6. Scheduler scheduler = schedulerFactory.getScheduler();
    7. // 3、创建JobDetail实例,并把任务详情对象和我们自己定义的任务绑定
    8. JobDetail jobDetail = JobBuilder.newJob(HelloJob.class).withIdentity("job1", "group1").build(); // withIdentity():给任务分组
    9. // 4、构建CornTrigger(触发器)实例
    10. Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "triggerGroup1").startNow() // 立即生效
    11. // 创建一个cron触发器
    12. .withSchedule(CronScheduleBuilder.cronSchedule(" */3 * * * * ? ")).build(); // 一直执行
    13. // 5、进行调度
    14. scheduler.scheduleJob(jobDetail, trigger);
    15. System.out.println("--------scheduler start ! ----------- ");
    16. // 6、执行
    17. scheduler.start();
    18. // 睡眠
    19. TimeUnit.MINUTES.sleep(1);
    20. scheduler.shutdown();
    21. System.out.println("--------scheduler shutdown ! ------------");
    22. }