一、简介
1.概述
OpenSymphony所提供的Quartz自2001年发布版本以来已经被众多项目作为任务调度的解决方案,Quartz在提供巨大灵活性的同时并未牺牲其简单性,它所提供的强大功能使你可以应付绝大多数的调度需求。是开源任务调度框架中的翘首,它提供了强大任务调度机制,难能可贵的是它同时保持了使用的简单性。Quartz
允许开发人员灵活地定义触发器的调度时间表,并可以对触发器和任务进行关联映射。
2.作用
- 应用程序需要在给定时间执行任务,或者系统有连续维护作业
- 系统维护:调度工作给数据库的内容,每个工作日(节假日除外平日)在11:30 PM转储到一个XML文件中。
- 在应用程序内提供提醒服务 例如:定时备份数据库
二、Quartz体系结构
核心概念
任务
Job
:是一个接口,开发者实现该接口定义运行任务
JobDetail
:Quartz在每次执行Job时,都重新创建一个Job实例,所以它不直接接受一个Job的实例,相反它接收一个Job实现 类(实现类的Class对象),以便运行时通过newInstance()的反射机制实例化Job。因此需要通过一个类来描述Job的实现类及其它相关的静态信息,如Job名字、描 述、关联监听器等信息,JobDetail承担了这一角色
Trigger
定义:是一个类,描述触发Job执行的时间触发规则,主要有SimpleTrigger和CronTrigger这两个子类
SimpleTrigger
:当仅需触发一次或者以固定时间间隔周期执行
CronTrigger
:可以通过Cron表达式定义出各种复杂时间规则的调度方案:如每早晨9:00执行,周一、周三、周五下午5:00执行等
Calendar
:它是一些日历特定时间点的集合,一个Trigger可以和多个Calendar关联,以便排除或 包含某些时间点。
调度器
Scheduler
:代表一个Quartz的独立运行容器,Trigger和JobDetail可以注册到Scheduler中,两者在 Scheduler中拥有各自的组及名称,组及名称是Scheduler查找定位容器中某一对象的依据,Trigger的组及名称必须唯 一,JobDetail的组和名称也必须唯一。Scheduler定义了多个接口方法, 允许外部通过组及名称访问和控制容器中Trigger和JobDetail。一个Job可以对应 多个Trigger,但一个Trigger只能对应一个Job。
描述了Scheduler的内部组件结构,SchedulerContext提供Scheduler全局可见的上下文信息,每一个任务都对应一个JobDataMap,虚线表达的JobDataMap表示对应有状态的任务。一个Scheduler可以拥有多个Triger组和多个JobDetail组,注册Trigger和JobDetail时,如果不显式指定所属的 组,Scheduler将放入到默认组中,默认组的组名为Scheduler.DEFAULT_GROUP。组名和名称组成了对象的全名,同一类型对象的 全名不能相同。
Scheduler本身就是一个容器,它维护着Quartz的各种组件并实施调度的规则。Scheduler还拥有一个线程池,线程池为任务提供执 行线程——这比执行任务时简单地创建一个新线程要拥有更高的效率,同时通过共享节约资源的占用。通过线程池组件的支持,对于繁忙度高、压力大的任务调 度,Quartz将可以提供良好的伸缩性。
三、案例
1.SimpleTrigger
构造函数
SimpleTrigger(String name, String group)
:通过该构造函数指定Trigger所属组和名称;
SimpleTrigger(String name, String group, Date startTime)
:除指定Trigger所属组和名称外,还可以指定触发的开发时间;
SimpleTrigger(String name, String group, Date startTime, Date endTime, int repeatCount, long repeatInterval)
:除指定以上信息外,还可以指定结束时间、重复执行次数、时间间隔等参数;
SimpleTrigger(String name, String group, String jobName, String jobGroup, Date startTime, Date endTime, int repeatCount, long repeatInterval)
:这是最复杂的一个构造函数,在指定触发参数的同时,还通过jobGroup和jobName,让该Trigger和 Scheduler中的某个任务关联起来。
环境搭建
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>2.3.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.9</version>
<scope>test</scope>
</dependency>
2.CronTrigger
CronTrigger 能够提供比 SimpleTrigger 更有具体实际意义的调度方案,调度规则基于 Cron 表达式,CronTrigger 支持日历相关的重复时间间隔(比如每月第一个周一执行),而不是简单的周期时间间隔。因此,相对于SimpleTrigger而 言,CronTrigger在使用上也要复杂一些。
Cron表达式
Quartz使用类似于Linux下的Cron表达式定义时间规则,
Cron表达式由6或7个由空格分隔的时间字段组成,如表1所示: X X X X X X [X]
位置 | 字段范围 | 范围 | 允许的特使字符 |
---|---|---|---|
1 | 秒 | 0~59 | * / |
2 | 分钟 | 0~59 | * / |
3 | 小时 | 0~23 | * / |
4 | 月份中的哪一天 | 1~31 | * / ? L W |
5 | 月份 | 1~12 或 JAN~DEC | * / |
6 星期几 | 1~7 或 SUN~SAT | * / ? L # | |
7 | 年份 | 1970~2099 | * / |
Cron表达式的时间字段除允许设置数值外,还可使用一些特殊的字符,提供列表、范围、通配符等功能
●星号(*)
:可用在所有字段中,表示对应时间域的每一个时刻,例如,在分钟字段时,表示“每分钟”;
●问号(?)
:该字符只在日期和星期字段中使用,它通常指定为“无意义的值”,相当于点位符;
●减号(-)
:表达一个范围,如在小时字段中使用“10-12”,则表示从10到12点,即10,11,12;
●逗号(,)
:表达一个列表值,如在星期字段中使用“MON,WED,FRI”,则表示星期一,星期三和星期五;
●斜杠(/)
:x/y表达一个等步长序列,x为起始值,y为增量步长值。如在秒字段中使用0/15,则表示为0,15,30和45秒,而5/15在分钟字段中表示5,20,35,50,你也可以使用/y,它等同于0/y;
●L
:该字符只在日期和星期字段中使用,代表“Last”的意思,但它在两个字段中意思不同。L在日期字段中,表示这个月份的最后一天,如一月的 31号,非闰年二月的28号;如果L用在星期中,则表示星期六,等同于7。但是,如果L出现在星期字段里,而且在前面有一个数值X,则表示“这个月的最后 X天”,例如,6L表示该月的最后星期五;
●W
:该字符只能出现在日期字段里,是对前导日期的修饰,表示离该日期最近的工作日。例如15W表示离该月15号最近的工作日,如果该月15号是星 期六,则匹配14号星期五;如果15日是星期日,则匹配16号星期一;如果15号是星期二,那结果就是15号星期二。但必须注意关联的匹配日期不能够跨 月,如你指定1W,如果1号是星期六,结果匹配的是3号星期一,而非上个月最后的那天。W字符串只能指定单一日期,而不能指定日期范围;
●LW组合
:在日期字段可以组合使用LW,它的意思是当月的最后一个工作日;
●井号(#)
:该字符只能在星期字段中使用,表示当月某个工作日。如6#3表示当月的第三个星期五(6表示星期五,#3表示当前的第三个),而4#5表示当月的第五个星期三,假设当月没有第五个星期三,忽略不触发;
● C
:该字符只在日期和星期字段中使用,代表“Calendar”的意思。它的意思是计划所关联的日期,如果日期没有被关联,则相当于日历中所有日期。例如5C在日期字段中就相当于日历5日以后的第一天。1C在星期字段中相当于星期日后的第一天。
3.Calendar
Quartz 提供了一个Calendar对象,(注意不是java.util.Calendar),并用它关联触发器,在指定的时间内不执行任务,例如:我们可以在每一个周末创建一个trigger对象,但是不包含法定的节假日。
以下是Quartz给我们提供的日历管理控件:
名称 | 含义 |
---|---|
AnnualCalendar | This implementation of the Calendar excludes a set of days of the year. |
BaseCalendar | This implementation of the Calendar may be used (you don’t have to) as a base class for more sophisticated one’s. |
CronCalendar | This implementation of the Calendar excludes the set of times expressed by a given CronExpression. |
DailyCalendar | This implementation of the Calendar excludes (or includes - see below) a specified time range each day. |
HolidayCalendar | This implementation of the Calendar stores a list of holidays (full days that are excluded from scheduling). |
MonthlyCalendar | This implementation of the Calendar excludes a set of days of the month. |
WeeklyCalendar | This implementation of the Calendar excludes a set of days of the week. |
AnnualCalendar案例,避开五一,国庆执行
public static void main(String[] args) throws SchedulerException, InterruptedException {
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
Calendar calendar = new GregorianCalendar();
calendar.set(Calendar.MONTH, 4);
calendar.set(Calendar.DATE, 1);
AnnualCalendar holidays = new AnnualCalendar();
holidays.setDayExcluded(calendar, true);
Calendar calendar2 = new GregorianCalendar();
calendar2.set(Calendar.MONTH, 9);
calendar2.set(Calendar.DATE, 1);
holidays.setDayExcluded(calendar2, true);
//不触发trigger触发器
scheduler.addCalendar("myHolidays", holidays, false, false);
Trigger trigger = TriggerBuilder.newTrigger().
withIdentity("myTrigger", "group").
startNow().withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3)
.repeatForever()).modifiedByCalendar("myHolidays").build();
JobDetail jobDetail = JobBuilder.newJob(MyJob.class).build();
scheduler.scheduleJob(jobDetail,trigger);
scheduler.start();
}
CronCalendar案例
public static void main(String[] args) throws SchedulerException, InterruptedException, ParseException {
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
///在早8点-晚5点触发,其他时间暂停。
CronCalendar holidays = new CronCalendar("* * 0-7,18-23 ? * * ");
//不触发trigger触发器
scheduler.addCalendar("myHolidays", holidays, false, false);
Trigger trigger = TriggerBuilder.newTrigger().
withIdentity("myTrigger", "group").
startNow().withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3)
.repeatForever()).modifiedByCalendar("myHolidays").build();
JobDetail jobDetail = JobBuilder.newJob(MyJob.class).build();
scheduler.scheduleJob(jobDetail,trigger);
scheduler.start();
}
四、整合spring
1.Quartz Schduler配置作业
A:使用 MethodInvokingJobDetailFactoryBean
这种方式在你想要调用特定 bean 的一个方法的时候很是方便,比另一种方法要简单的多。
B:使用 JobDetailFactoryBean
如果你需要更高级的设置,需要给作业传递数据,想更加灵活的话就使用这种方式.
jobClass 关联到一个继承自 QuartzJobBean 的类,它实现了 Quartz 作业接口。调用到这个作业的时候,它的 executeInternal 将被执行。
2.配置 Quartz 调度时要使用到的触发器
触发器用来定义调度器何时将会执行你的调度任务的那个时间。有两种可能的触发器类型:
A:简单触发器,使用 SimpleTriggerFactoryBean
你可以定义作业的启动时间、触发器之间的延迟时间以及 repeatInterval(频率)。
B:计划触发器,使用 CronTriggerFactoryBean
这种类型更加灵活,允许你针对特定实例选择计划方案以及将来要执行的频率。
3.配置创建定配置 Quartz 调度器的 SchedulerFactoryBean
SchedulerFactoryBean
将 jobDetails 和 triggers 整合在一起以配置 Quartz 调度器。