目录
一、介绍
二、背景知识
三、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接口。它提供了简单的操作,例如:将任务纳入日程或者从日程中取消,开始/停止/暂停日程进度。
同类产品:
Quartz是一个任务调度框架。比如你遇到这样的问题
这些问题总结起来就是:在某一个有规律的时间点干某件事。并且时间的触发的条件可以非常复杂(比如每月最后一个工作日的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的策略实际上都是解答两个问题:
- 已经MisFire的任务还要重新触发吗?
- 如果发生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去决定”。基本策略是,
- 如果是只执行一次的调度,使用MISFIRE_INSTRUCTION_FIRE_NOW
- 如果是无限次的调度(repeatCount是无限的),使用MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT
- 否则,使用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、创建调度工厂(); //工厂模式
2、根据工厂取得调度器实例(); //工厂模式
3、Builder模式构建子组件
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
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 WED | 3月每周三的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