热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Quartz笔记

目录一、介绍二、背景知识三、Quartz可以用来做什么?五、核心概念Job并发JobExecutionExceptionJobDataMapSimpl

目录

一、介绍

二、背景知识

三、Quartz可以用来做什么?

五、核心概念

Job并发

JobExecutionException

JobDataMap

SimpleTrigger

CalendarIntervalTrigger

DailyTimeIntervalTrigger

CronTrigger

关于name和group

StartTime & EndTime

优先级(Priority)

Misfire(错失触发)策略

 六、使用流程

八、表关系和解释

九、cron表达式编写规则

十、总结



这几天接触到了Quartz,简单的做下笔记吧,好记性不如烂笔头。本文主要围绕Quartz的简单使用以及基本原理理解大多知识是从别的博客趴过来的,文章的最后会附上原文链接^_^。

Quartz-JOB-Framework 中文版和QUartz开发指南:https://download.csdn.net/download/sinat_37064286/11423292


一、介绍

Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,是完全由java开发的一个开源的任务日程管理系统,“任务进度管理器”就是一个在预先确定(被纳入日程)的时间到达时,负责执行(或者通知)其他软件组件的系统。
Quartz用一个小Java库发布文件(.jar文件),这个库文件包含了所有Quartz核心功能。这些功能的主要接口(API)是Scheduler接口。它提供了简单的操作,例如:将任务纳入日程或者从日程中取消,开始/停止/暂停日程进度。


二、背景知识

同类产品:


  1. Java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务。使用这种方式可以让你的程序按照某一个频度执行,但不能在指定时间运行。一般用的较少,这篇文章将不做详细介绍。
  2. 使用Quartz,这是一个功能比较强大的的调度器,可以让你的程序在指定时间执行,也可以按照某一个频度执行,配置起来稍显复杂,稍后会详细介绍。
  3. Spring3.0以后自带的task,可以将它看成一个轻量级的Quartz,而且使用起来比Quartz简单许多,稍后会介绍。

三、Quartz可以用来做什么?

Quartz是一个任务调度框架。比如你遇到这样的问题


  • 想每月25号,信用卡自动还款
  • 想每年4月1日自己给当年暗恋女神发一封匿名贺卡
  • 想每隔1小时,备份一下自己的爱情动作片 学习笔记到云盘

这些问题总结起来就是:在某一个有规律的时间点干某件事。并且时间的触发的条件可以非常复杂(比如每月最后一个工作日的17:50),复杂到需要一个专门的框架来干这个事。 Quartz就是来干这样的事,你给它一个触发条件的定义,它负责到了时间点,触发相应的Job起来干活。


五、核心概念

Quartz的原理不是很复杂,只要搞明白几个概念,然后知道如何去启动和关闭一个调度程序即可。

1、Job

表示一个工作,要执行的具体内容。此接口中只有一个方法 :void execute(JobExecutionContext context)。主要有两种类型的 job:无状态的(stateless)和有状态的(stateful)。对于同一个 trigger 来说,有状态的 job 不能被并行执行,只有上一次触发的任务被执行完之后,才能触发下一次执行。Job 主要有两种属性:volatility 和 durability,其中 volatility 表示任务是否被持久化到数据库存储,而 durability 表示在没有 trigger 关联的时候任务是否被保留。两者都是在值为 true 的时候任务被持久化或保留。一个 job 可以被多个 trigger 关联,但是一个 trigger 只能关联一个 job。


Job并发

Job是有可能并发执行的,比如一个任务要执行10秒中,而调度算法是每秒中触发1次,那么就有可能多个任务被并发执行。

有时候我们并不想任务并发执行,比如这个任务要去”获得数据库中所有未发送邮件的名单“,如果是并发执行,就需要一个数据库锁去避免一个数据被多次处理。这个时候一个@DisallowConcurrentExecution解决这个问题。

就是这样

public class DoNothingJob implements Job {@DisallowConcurrentExecutionpublic void execute(JobExecutionContext context) throws JobExecutionException {System.out.println("do nothing");}
}

注意,@DisallowConcurrentExecution是对JobDetail实例生效,也就是如果你定义两个JobDetail,引用同一个Job类,是可以并发执行的。

JobExecutionException

Job.execute()方法是不允许抛出除JobExecutionException之外的所有异常的(包括RuntimeException),所以编码的时候,最好是try-catch住所有的Throwable,小心处理。


2、JobDetail

JobDetail表示一个具体的可执行的调度程序,Job是这个可执行程调度程序所要执行的内容,另外JobDetail还包含了这个任务调度的方案和策略。


JobDetail & Job区别

JobDetail 定义的是任务数据,而真正的执行逻辑是在Job中,例子中是HelloQuartz。 为什么设计成JobDetail + Job,不直接使用Job?这是因为任务是有可能并发执行,如果Scheduler直接使用Job,就会存在对同一个Job实例并发访问的问题。而JobDetail & Job 方式,sheduler每次执行,都会根据JobDetail创建一个新的Job实例,这样就可以规避并发访问的问题。

JobDataMap

在Quartz中,每次Scheduler执行Job时,在调用其execute()方法之前,它需要先根据JobDetail提供的Job类型创建一个Job class的实例,在任务执行完以后,Job class的实例会被丢弃,Jvm的垃圾回收器会将它们回收。因此编写Job的具体实现时,需要注意:

(1) 它必须具有一个无参数的构造函数;

(2) 它不应该有静态数据类型,因为每次Job执行完以后便被回收,因此在多次执行时静态数据没法被维护。

  Job每次都是newInstance的实例,那我怎么传值给它? 比如我现在有两个发送邮件的任务,一个是发给"liLei",一个发给"hanmeimei",不能说我要写两个Job实现类LiLeiSendEmailJob和HanMeiMeiSendEmailJob。实现的办法是通过JobDataMap。

每一个JobDetail都会有一个JobDataMap。JobDataMap本质就是一个Map的扩展类,只是提供了一些更便捷的方法,比如getString()之类的。

我们可以在定义JobDetail,加入属性值,方式有二:

newJob().usingJobData("age", 18) //加入属性到ageJobDataMaporjob.getJobDataMap().put("name", "quertz"); //加入属性name到JobDataMap

然后在Job中可以获取这个JobDataMap的值,方式同样有二:

public class HelloQuartz implements Job {private String name;public void execute(JobExecutionContext context) throws JobExecutionException {JobDetail detail = context.getJobDetail();JobDataMap map = detail.getJobDataMap(); //方法一:获得JobDataMapSystem.out.println("say hello to " + name + "[" + map.getInt("age") + "]" + " at "+ new Date());}//方法二:属性的setter方法,会将JobDataMap的属性自动注入public void setName(String name) { this.name = name;}
}

对于同一个JobDetail实例,执行的多个Job实例,是共享同样的JobDataMap,也就是说,如果你在任务里修改了里面的值,会对其他Job实例(并发的或者后续的)造成影响。

除了JobDetail,Trigger同样有一个JobDataMap,共享范围是所有使用这个Trigger的Job实例。


3、Trigger代表一个调度参数的配置,什么时候去调,用于定义任务调度时间规则。Quartz 中主要提供了四种类型的 trigger:SimpleTrigger,CronTirgger,DateIntervalTrigger,和 NthIncludedDayTrigger。这四种 trigger 可以满足企业应用中的绝大部分需求。


数据存储

Quartz 中的 trigger 和 job 需要存储下来才能被使用。Quartz 中有两种存储方式:RAMJobStore, JobStoreSupport。

RAMJobStore    不要外部数据库,配置容易,运行速度快    因为调度程序信息是存储在被分配给JVM的内存里面,所以,当应用程序停止运行时,所有调度信息将被丢失。另外因为存储到JVM内存里面,所以可以存储多少个Job和Trigger将会受到限制
JDBCJobStore    支持集群,因为所有的任务信息都会保存到数据库中,可以控制事物,还有就是如果应用服务器关闭或者重启,任务信息都不会丢失,并且可以恢复因服务器关闭或者重启而导致执行失败的任务    运行速度的快慢取决与连接数据库的快慢

在 Quartz 中,JobStoreSupport 使用一个驱动代理来操作 trigger 和 job 的数据存储:StdJDBCDelegate。StdJDBCDelegate 实现了大部分基于标准 JDBC 的功能接口,但是对于各种数据库来说,需要根据其具体实现的特点做某些特殊处理,因此各种数据库需要扩展 StdJDBCDelegate 以实现这些特殊处理。Quartz 已经自带了一些数据库的扩展实现,可以直接使用。

种类

SimpleTrigger

指定从某一个时间开始,以一定的时间间隔(单位是毫秒)执行的任务。

它适合的任务类似于:9:00 开始,每隔1小时,执行一次。

它的属性有:

  • repeatInterval 重复间隔
  • repeatCount 重复次数。实际执行次数是 repeatCount+1。因为在startTime的时候一定会执行一次。** 下面有关repeatCount 属性的都是同理。 **

例子:

simpleSchedule().withIntervalInHours(1) //每小时执行一次.repeatForever() //次数不限.build();simpleSchedule().withIntervalInMinutes(1) //每分钟执行一次.withRepeatCount(10) //次数为10次.build();

CalendarIntervalTrigger

类似于SimpleTrigger,指定从某一个时间开始,以一定的时间间隔执行的任务。 但是不同的是SimpleTrigger指定的时间间隔为毫秒,没办法指定每隔一个月执行一次(每月的时间间隔不是固定值),而CalendarIntervalTrigger支持的间隔单位有秒,分钟,小时,天,月,年,星期

相较于SimpleTrigger有两个优势:1、更方便,比如每隔1小时执行,你不用自己去计算1小时等于多少毫秒。 2、支持不是固定长度的间隔,比如间隔为月和年。但劣势是精度只能到秒。

它适合的任务类似于:9:00 开始执行,并且以后每周 9:00 执行一次

它的属性有:

  • interval 执行间隔
  • intervalUnit 执行间隔的单位(秒,分钟,小时,天,月,年,星期)

例子:

calendarIntervalSchedule().withIntervalInDays(1) //每天执行一次.build();calendarIntervalSchedule().withIntervalInWeeks(1) //每周执行一次.build();

DailyTimeIntervalTrigger

指定每天的某个时间段内,以一定的时间间隔执行任务。并且它可以支持指定星期。

它适合的任务类似于:指定每天9:00 至 18:00 ,每隔70秒执行一次,并且只要周一至周五执行。

它的属性有:

  • startTimeOfDay 每天开始时间
  • endTimeOfDay 每天结束时间
  • daysOfWeek 需要执行的星期
  • interval 执行间隔
  • intervalUnit 执行间隔的单位(秒,分钟,小时,天,月,年,星期)
  • repeatCount 重复次数

例子:

dailyTimeIntervalSchedule().startingDailyAt(TimeOfDay.hourAndMinuteOfDay(9, 0)) //第天9:00开始.endingDailyAt(TimeOfDay.hourAndMinuteOfDay(16, 0)) //16:00 结束 .onDaysOfTheWeek(MONDAY,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY) //周一至周五执行.withIntervalInHours(1) //每间隔1小时执行一次.withRepeatCount(100) //最多重复100次(实际执行100+1次).build();dailyTimeIntervalSchedule().startingDailyAt(TimeOfDay.hourAndMinuteOfDay(9, 0)) //第天9:00开始.endingDailyAfterCount(10) //每天执行10次,这个方法实际上根据 startTimeOfDay+interval*count 算出 endTimeOfDay.onDaysOfTheWeek(MONDAY,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY) //周一至周五执行.withIntervalInHours(1) //每间隔1小时执行一次.build();

CronTrigger

适合于更复杂的任务,它支持类型于Linux Cron的语法(并且更强大)。基本上它覆盖了以上三个Trigger的绝大部分能力(但不是全部)—— 当然,也更难理解。

它适合的任务类似于:每天0:00,9:00,18:00各执行一次。

它的属性只有:

  • Cron表达式。但这个表示式本身就够复杂了。下面会有说明。

例子:

cronSchedule("0 0/2 8-17 * * ?") // 每天8:00-17:00,每隔2分钟执行一次.build();cronSchedule("0 30 9 ? * MON") // 每周一,9:30执行一次
.build();weeklyOnDayAndHourAndMinute(MONDAY,9, 30) //等同于 0 30 9 ? * MON .build();

关于name和group

JobDetail和Trigger都有name和group。

name是它们在这个sheduler里面的唯一标识。如果我们要更新一个JobDetail定义,只需要设置一个name相同的JobDetail实例即可。

group是一个组织单元,sheduler会提供一些对整组操作的API,比如 scheduler.resumeJobs()。

StartTime & EndTime

startTime和endTime指定的Trigger会被触发的时间区间。在这个区间之外,Trigger是不会被触发的。

** 所有Trigger都会包含这两个属性 **

优先级(Priority)

当scheduler比较繁忙的时候,可能在同一个时刻,有多个Trigger被触发了,但资源不足(比如线程池不足)。那么这个时候比剪刀石头布更好的方式,就是设置优先级。优先级高的先执行。

需要注意的是,优先级只有在同一时刻执行的Trigger之间才会起作用,如果一个Trigger是9:00,另一个Trigger是9:30。那么无论后一个优先级多高,前一个都是先执行。

优先级的值默认是5,当为负数时使用默认值。最大值似乎没有指定,但建议遵循Java的标准,使用1-10,不然鬼才知道看到【优先级为10】是时,上头还有没有更大的值。

Misfire(错失触发)策略

类似的Scheduler资源不足的时候,或者机器崩溃重启等,有可能某一些Trigger在应该触发的时间点没有被触发,也就是Miss Fire了。这个时候Trigger需要一个策略来处理这种情况。每种Trigger可选的策略各不相同。

这里有两个点需要重点注意:

  • MisFire的触发是有一个阀值,这个阀值是配置在JobStore的。比RAMJobStore是org.quartz.jobStore.misfireThreshold。只有超过这个阀值,才会算MisFire。小于这个阀值,Quartz是会全部重新触发。

所有MisFire的策略实际上都是解答两个问题:

  1. 已经MisFire的任务还要重新触发吗?
  2. 如果发生MisFire,要调整现有的调度时间吗?

比如SimpleTrigger的MisFire策略有:

  • MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY

    这个不是忽略已经错失的触发的意思,而是说忽略MisFire策略。它会在资源合适的时候,重新触发所有的MisFire任务,并且不会影响现有的调度时间。

    比如,SimpleTrigger每15秒执行一次,而中间有5分钟时间它都MisFire了,一共错失了20个,5分钟后,假设资源充足了,并且任务允许并发,它会被一次性触发。

    这个属性是所有Trigger都适用。

  • MISFIRE_INSTRUCTION_FIRE_NOW

    忽略已经MisFire的任务,并且立即执行调度。这通常只适用于只执行一次的任务。

  • MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT

    将startTime设置当前时间,立即重新调度任务,包括的MisFire的

  • MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT

    类似MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT,区别在于会忽略已经MisFire的任务

  • MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT

    在下一次调度时间点,重新开始调度任务,包括的MisFire的

  • MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT

    类似于MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT,区别在于会忽略已经MisFire的任务。

  • MISFIRE_INSTRUCTION_SMART_POLICY

    所有的Trigger的MisFire默认值都是这个,大致意思是“把处理逻辑交给聪明的Quartz去决定”。基本策略是,

    1. 如果是只执行一次的调度,使用MISFIRE_INSTRUCTION_FIRE_NOW
    2. 如果是无限次的调度(repeatCount是无限的),使用MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT
    3. 否则,使用MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT

MisFire的东西挺繁杂的,可以参考这篇


4、Scheduler代表一个调度容器,一个调度容器中可以注册多个JobDetail和Trigger。当Trigger与JobDetail组合,就可以被Scheduler容器调度了。 scheduler 由 scheduler 工厂创建:DirectSchedulerFactory 或者 StdSchedulerFactory。 第二种工厂 StdSchedulerFactory 使用较多,因为 DirectSchedulerFactory 使用起来不够方便,需要作许多详细的手工编码设置。 Scheduler 主要有三种:RemoteMBeanScheduler, RemoteScheduler 和 StdScheduler。本文以最常用的 StdScheduler 为例讲解。

5、misfire:错过的,指本来应该被执行但实际没有被执行的任务调度

 Quartz 核心元素关系图

图 1. Quartz 核心元素关系图


 六、使用流程

   1、创建调度工厂();      //工厂模式

   2、根据工厂取得调度器实例();    //工厂模式

   3、Builder模式构建子组件    // builder模式, 如JobBuilder、TriggerBuilder、DateBuilder

   4、通过调度器组装子组件   调度器.组装<子组件1,子组件2...>    //工厂模式

   5、调度器.start();   //工厂模式

七、一个最简单入门实例

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

import java.util.Date; 

/** 
* quartz定时器测试 

* &#64;author leizhimin 2009-7-23 8:49:01 
*/
 
public class MyJob implements Job { 
        public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { 
                System.out.println(new Date() &#43; ": doing something..."); 

System.out.println("现在的时间是&#xff1a;"&#43; sf.format(date));//具体的业务逻辑System.out.println("开始生成任务报表 或 开始发送邮件....");JobKey key &#61; jobExecutionContext.getJobDetail().getKey();System.out.println("jobDetail 的name &#xff1a; "&#43;key.getName()); //打印jobDetail 的nameSystem.out.println("jobDetail 的group &#xff1a; "&#43;key.getGroup()); //打印jobDetail 的groupJobDataMap jobDetailDataMap &#61; jobExecutionContext.getJobDetail().getJobDataMap();String message &#61; jobDetailDataMap.getString("message"); //float floatJobValue &#61; jobDetailDataMap.getFloat("FloatJobValue");System.out.println("jobDataMap定义的message的值 : "&#43;message ); //打印jobDataMap定义的message的值 System.out.println("jobDataMap定义的floatJobValue的值 : "&#43;floatJobValue ); //jobDataMap定义的floatJobValue的值} 
} class Test { public static void main(String[] args) { //1、创建JobDetial对象 JobDetail jobDetail &#61; new JobDetail(); //设置工作项 jobDetail.setJobClass(MyJob.class); jobDetail.setName("MyJob_1"); jobDetail.setGroup("JobGroup_1"); //2、创建Trigger对象 SimpleTrigger strigger &#61; new SimpleTrigger(); strigger.setName("Trigger_1"); strigger.setGroup("Trigger_Group_1"); strigger.setStartTime(new Date()); //设置重复停止时间&#xff0c;并销毁该Trigger对象 java.util.Calendar c &#61; java.util.Calendar.getInstance(); c.setTimeInMillis(System.currentTimeMillis() &#43; 1000 * 1L); strigger.setEndTime(c.getTime()); strigger.setFireInstanceId("Trigger_1_id_001"); //设置重复间隔时间 strigger.setRepeatInterval(1000 * 1L); //设置重复执行次数 strigger.setRepeatCount(3); //3、创建Scheduler对象&#xff0c;并配置JobDetail和Trigger对象 SchedulerFactory sf &#61; new StdSchedulerFactory(); Scheduler scheduler &#61; null; try { scheduler &#61; sf.getScheduler(); scheduler.scheduleJob(jobDetail, strigger); //4、并执行启动、关闭等操作 scheduler.start(); } catch (SchedulerException e) { e.printStackTrace(); } 
//                try { 
//                        //关闭调度器 
//                        scheduler.shutdown(true); 
//                } catch (SchedulerException e) { 
//                        e.printStackTrace(); 
//                } } 
}

 

执行结果&#xff1a;

 

当把结束时间改为&#xff1a;

                //设置重复停止时间&#xff0c;并销毁该Trigger对象 
                java.util.Calendar c &#61; java.util.Calendar.getInstance(); 
                c.setTimeInMillis(System.currentTimeMillis() &#43; 1000 * 1L); 
                strigger.setEndTime(c.getTime());

 

执行结果&#xff1a;

 

当添加一条关闭调度器的语句&#xff1a;

                        //4、并执行启动、关闭等操作 
                        scheduler.start(); 
                        scheduler.shutdown(true); 

 

程序执行结果&#xff1a;

Thu Jul 23 10:11:50 CST 2009: doing something... 

Process finished with exit code 0

仅仅执行了一次&#xff0c;这一次能执行完&#xff0c;原因是设定了scheduler.shutdown(true);true表示等待本次任务执行完成后停止。

 从这里也可以看出&#xff0c;scheduler是个容器&#xff0c;scheduler控制jobDetail的执行&#xff0c;控制的策略是通过trigger。

当scheduler容器启动后&#xff0c;jobDetail才能根据关联的trigger策略去执行。当scheduler容器关闭后&#xff0c;所有的jobDetail都停止执行。


八、表关系和解释

表关系

è¿éåå¾çæè¿°
qrtz_blob_triggers    Trigger作为Blob类型存储(用于Quartz用户用JDBC创建他们自己定制的Trigger类型&#xff0c;JobStore 并不知道如何存储实例的时候)
qrtz_calendars    以Blob类型存储Quartz的Calendar日历信息&#xff0c; quartz可配置一个日历来指定一个时间范围
qrtz_cron_triggers    存储Cron Trigger&#xff0c;包括Cron表达式和时区信息。
qrtz_fired_triggers    存储与已触发的Trigger相关的状态信息&#xff0c;以及相联Job的执行信息
qrtz_job_details    存储每一个已配置的Job的详细信息
qrtz_locks    存储程序的非观锁的信息(假如使用了悲观锁)
qrtz_paused_trigger_graps    存储已暂停的Trigger组的信息
qrtz_scheduler_state    存储少量的有关 Scheduler的状态信息&#xff0c;和别的 Scheduler 实例(假如是用于一个集群中)
qrtz_simple_triggers    存储简单的 Trigger&#xff0c;包括重复次数&#xff0c;间隔&#xff0c;以及已触的次数
qrtz_triggers    存储已配置的 Trigger的信息
qrzt_simprop_triggers    

 


九、cron表达式编写规则

 1. Quartz Cron 表达式支持7个域 ,分别是秒/分/时/日/月/周/年.期中年是非必须项.如下图


名称是否必须允许值特殊字符
0-59, - * /
0-59, - * /
0-23, - * /
1-31, - * ? / L W C
1-12 或 JAN-DEC, - * /
1-7 或 SUN-SAT, - * ? / L C #
空 或 1970-2099, - * /

注意在cron表达式中不区分大小写.

星号(*)&#xff1a;可用在所有字段中&#xff0c;表示对应时间域的每一个时刻&#xff0c;例如&#xff0c; 在分钟字段时&#xff0c;表示“每分钟”&#xff1b;

问号&#xff08;?&#xff09;&#xff1a;该字符只在日期和星期字段中使用&#xff0c;它通常指定为“无意义的值”&#xff0c;相当于点位符&#xff1b;

减号(-)&#xff1a;表达一个范围&#xff0c;如在小时字段中使用“10-12”&#xff0c;则表示从10到12点&#xff0c;即10,11,12&#xff1b;

逗号(,)&#xff1a;表达一个列表值&#xff0c;如在星期字段中使用“MON,WED,FRI”&#xff0c;则表示星期一&#xff0c;星期三和星期五&#xff1b;

斜杠(/)&#xff1a;x/y表达一个等步长序列&#xff0c;x为起始值&#xff0c;y为增量步长值。如在分钟字段中使用0/15&#xff0c;则表示为0,15,30和45秒&#xff0c;而5/15在分钟字段中表示5,20,35,50&#xff0c;你也可以使用*/y&#xff0c;它等同于0/y&#xff1b;

L&#xff1a;该字符只在日期和星期字段中使用&#xff0c;代表“Last”的意思&#xff0c;但它在两个字段中意思不同。L在日期字段中&#xff0c;表示这个月份的最后一天&#xff0c;如一月的31号&#xff0c;非闰年二月的28号&#xff1b;如果L用在星期中&#xff0c;则表示星期六&#xff0c;等同于7。但是&#xff0c;如果L出现在星期字段里&#xff0c;而且在前面有一个数值X&#xff0c;则表示“这个月的最后X天”&#xff0c;例如&#xff0c;6L表示该月的最后星期五&#xff1b;

W&#xff1a;该字符只能出现在日期字段里&#xff0c;是对前导日期的修饰&#xff0c;表示离该日期最近的工作日。例如15W表示离该月15号最近的工作日&#xff0c;如果该月15号是星期六&#xff0c;则匹配14号星期五&#xff1b;如果15日是星期日&#xff0c;则匹配16号星期一&#xff1b;如果15号是星期二&#xff0c;那结果就是15号星期二。但必须注意关联的匹配日期不能够跨月&#xff0c;如你指定1W&#xff0c;如果1号是星期六&#xff0c;结果匹配的是3号星期一&#xff0c;而非上个月最后的那天。W字符串只能指定单一日期&#xff0c;而不能指定日期范围&#xff1b;

LW组合&#xff1a;在日期字段可以组合使用LW&#xff0c;它的意思是当月的最后一个工作日&#xff1b;

井号(#)&#xff1a;该字符只能在星期字段中使用&#xff0c;表示当月某个工作日。如6#3表示当月的第三个星期五(6表示星期五&#xff0c;#3表示当前的第三个)&#xff0c;而4#5表示当月的第五个星期三&#xff0c;假设当月没有第五个星期三&#xff0c;忽略不触发&#xff1b;

C&#xff1a;该字符只在日期和星期字段中使用&#xff0c;代表“Calendar”的意思。它的意思是计划所关联的日期&#xff0c;如果日期没有被关联&#xff0c;则相当于日历中所有日期。例如5C在日期字段中就相当于日历5日以后的第一天。1C在星期字段中相当于星期日后的第一天。

Cron表达式对特殊字符的大小写不敏感&#xff0c;对代表星期的缩写英文大小写也不敏感。

2.官方的一些案例


表示式说明
0 0 12 * * ?每天12点运行
0 15 10 ? * *每天10:15运行
0 15 10 * * ?每天10:15运行
0 15 10 * * ? *每天10:15运行
0 15 10 * * ? 2008在2008年的每天10&#xff1a;15运行
0 * 14 * * ?每天14点到15点之间每分钟运行一次&#xff0c;开始于14:00&#xff0c;结束于14:59。
0 0/5 14 * * ?每天14点到15点每5分钟运行一次&#xff0c;开始于14:00&#xff0c;结束于14:55。
0 0/5 14,18 * * ?每天14点到15点每5分钟运行一次&#xff0c;此外每天18点到19点每5钟也运行一次。
0 0-5 14 * * ?每天14:00点到14:05&#xff0c;每分钟运行一次。
0 10,44 14 ? 3 WED3月每周三的14:10分到14:44&#xff0c;每分钟运行一次。
0 15 10 ? * MON-FRI每周一&#xff0c;二&#xff0c;三&#xff0c;四&#xff0c;五的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 2007-2009在2007,2008,2009年每个月的最后一个星期五的10:15分运行。
0 15 10 ? * 6#3每月第三个星期五的10:15分运行。

以上就可以实现大部分的业务的需求了&#xff0c;附上cron表达式在线生成器&#xff1a;https://www.pppet.net/


十、总结

1、搞清楚了上Quartz容器执行作业的的原理和过程&#xff0c;以及作业形成的方式&#xff0c;作业注册到容器的方法。就认识明白了Quartz的核心原理。

2、Quartz虽然很庞大&#xff0c;但是一切都围绕这个核心转&#xff0c;为了配置强大时间调度策略&#xff0c;可以研究专门的CronTrigger。要想灵活配置作业和容器属性&#xff0c;可以通过Quartz的properties文件或者XML来实现。

3、要想调度更多的持久化、结构化作业&#xff0c;可以通过数据库读取作业&#xff0c;然后放到容器中执行。

4、所有的一切都围绕这个核心原理转&#xff0c;搞明白这个了&#xff0c;再去研究更高级用法就容易多了。

5、Quartz与Spring的整合也非常简单&#xff0c;Spring提供一组Bean来支持&#xff1a;MethodInvokingJobDetailFactoryBean、SimpleTriggerBean、SchedulerFactoryBean&#xff0c;看看里面需要注入什么属性即可明白了。Spring会在Spring容器启动时候&#xff0c;启动Quartz容器。

6、Quartz容器的关闭方式也很简单&#xff0c;如果是Spring整合&#xff0c;则有两种方法&#xff0c;一种是关闭Spring容器&#xff0c;一种是获取到SchedulerFactoryBean实例&#xff0c;然后调用一个shutdown就搞定了。如果是Quartz独立使用&#xff0c;则直接调用scheduler.shutdown(true);

7、Quartz的JobDetail、Trigger都可以在运行时重新设置&#xff0c;并且在下次调用时候起作用。这就为动态作业的实现提供了依据。你可以将调度时间策略存放到数据库&#xff0c;然后通过数据库数据来设定Trigger&#xff0c;这样就能产生动态的调度。

参考博客&#xff1a;

https://blog.51cto.com/lavasoft/181907

https://www.cnblogs.com/drift-ice/p/3817269.html

https://www.cnblogs.com/zhanghaoliang/p/7886110.html

https://blog.csdn.net/u010648555/article/category/6601767

https://www.ibm.com/developerworks/cn/opensource/os-cn-quartz/

https://blog.csdn.net/u010648555/article/details/54863144


推荐阅读
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • Linux重启网络命令实例及关机和重启示例教程
    本文介绍了Linux系统中重启网络命令的实例,以及使用不同方式关机和重启系统的示例教程。包括使用图形界面和控制台访问系统的方法,以及使用shutdown命令进行系统关机和重启的句法和用法。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • 本文介绍了解决java开源项目apache commons email简单使用报错的方法,包括使用正确的JAR包和正确的代码配置,以及相关参数的设置。详细介绍了如何使用apache commons email发送邮件。 ... [详细]
  • 从批量eml文件中提取附件的Python代码实现方法
    本文介绍了使用Python代码从批量eml文件中提取附件的实现方法,包括获取eml附件信息、递归文件夹下所有文件、创建目的文件夹等步骤。通过该方法可以方便地提取eml文件中的附件,并保存到指定的文件夹中。 ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 本文介绍了在RHEL 7中的系统日志管理和网络管理。系统日志管理包括rsyslog和systemd-journal两种日志服务,分别介绍了它们的特点、配置文件和日志查询方式。网络管理主要介绍了使用nmcli命令查看和配置网络接口的方法,包括查看网卡信息、添加、修改和删除配置文件等操作。 ... [详细]
author-avatar
婷婷艾晨
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有