基本介绍
Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,可以与J2EE与J2SE应用程序相结合也可以单独使用。Quartz可以用来创建简单或为运行十个、百个、甚至是好几万个Jobs这样复杂的日程序表,Jobs可以做成标准的Java组件或EJBs
Quartz是一个任务日程管理系统,一个在预先确定(被纳入日程)的时间到达时,负责执行(或者通知)其他软件组件的系统
Quartz用一个小Java库发布文件(.jar文件),这个库文件包含了所有Quartz核心功能,这些功能的主要接口(API)是Scheduler接口,它提供了简单的操作
例如:将任务纳入日程或者从日程中取消,开始、停止、暂停日程进度
(1)完全由Java编写的开源作业调度框架,为在Java应用程序中进行作业调度提供了简单却强大的机制
(2)可以与J2EE与J2SE应用程序相结合也可以单独使用
(3)允许程序开发人员根据时间的间隔来调度作业
(4)实现了作业和触发器的多对多的关系,还能把多个作业与不同的触发器关联
(5)免费使用,并根据Apache 2.0许可
Quartz拥有完善的事件和监听体系,大部分组件都拥有事件,如任务执行前事件、任务执行后事件、触发器触发前事件、触发后事件、调度器开始事件、关闭事件等等,可以注册相应的监听器处理感兴趣的事件。
下载
1.进入官网
http://www.quartz-scheduler.org/
2.选择版本下载
3.文件目录说明
执行流程
Job为作业的接口,为任务调度的对象;JobDetail用来描述Job的实现类及其他相关的静态信息;Trigger做为作业的定时管理工具,一个Trigger只能对应一个作业实例,而一个作业实例可对应多个触发器;Scheduler做为定时任务容器,是Quartz最上层的东西,它提携了所有触发器和作业,使它们协调工作,每个Scheduler都存有JobDetail和Trigger的注册,一个Scheduler中可以注册多个JobDetail和多个Trigger
核心接口
(1)Job(任务接口,无状态)
开发者实现该接口定义运行任务。表示一个工作,要执行的具体内容。此接口中只有一个方法
public interface Job {
void execute(JobExecutionContext context) throws JobExecutionException;
}
Job是一个执行任务的简单java类。任务可以是任何java代码
只需实现org.quartz.Job接口,并将需要执行的任务代码写在execute()中即可。当Quartz确定该是作业运行的时候,它将调用作业。Quartz提供了一个机制来建立具有不同粒度的、可重复的调度表,于是,只需创建一个java类,这个类被调用而执行任务
类 | 描述 |
---|---|
JobExecutionContext类 | 提供了调度上下文的各种信息。Job运行时的信息保存在JobDataMap实例中 |
(2)StatefulJob(任务接口,有状态)
任务在执行任务过程中,任何对Job Data Map所作的更改都将丢失而且任务下次执行时也无法看到。
StatefulJob在任务的每次执行之后重新存储JobDataMap
Job vs. StatefulJob
JobDataMap在每次执行之后重新持久化到JobStore中。
两个或多个有状态的JobDetail实例不能并发执行
StatefulJob子接口
Job有一个StatefulJob子接口,代表有状态的任务,该接口是一个没有方法的标签接口,其目的是让Quartz知道任务的类型,以便采用不同的执行方案。无状态任务在执行时拥有自己的JobDataMap拷贝,对JobDataMap的更改不会影响下次的执行。而有状态任务共享共享同一个 JobDataMap实例,每次任务执行对JobDataMap所做的更改会保存下来,每次执行任务后都会对后面的执行发生影响。
正因为这个原因,无状态的Job可以并发执行,而有状态的StatefulJob不能并发执行,这意味着如果前次的StatefulJob还没有执行完毕,下一次的任务将阻塞等待,直到前次任务执行完毕。有状态任务比无状态任务需要考虑更多的因素,程序往往拥有更高的复杂度,因此除非必要,应该尽量使用无状态的Job
如果Quartz使用了数据库持久化任务调度信息,无状态的JobDataMap仅会在Scheduler注册任务时保持一次,而有状态任务对应的JobDataMap在每次执行任务后都会进行保存。
Trigger自身也可以拥有一个JobDataMap其关联的Job可以通过JobExecutionContext#getTrigger()
.getJobDataMap()获取Trigger中的JobDataMap。不管是有状态还是无状态的任务,在任务执行期间对Trigger的JobDataMap所做的更改都不会进行持久,也即不会对下次的执行产生影响。
(3)JobDetail(任务描述)
表示一个具体的可执行的调度程序。ob是这个可执行程调度程序所要执行的内容,另外JobDetail还包含了这个任务调度的方案和策略
Quartz在每次执行Job时,都重新创建一个Job实例,所以它不直接接受一个Job的实例,相反它接收一个Job实现类,以便运行时通过newInstance()的反射机制实例化Job。因此需要通过一个类来描述Job的实现类及其它相关的静态信息,如Job名字、描述、关联监听器等信息,JobDetail承担了这一角色
通过该类的构造函数可以更具体地了解它的功用:JobDetail(java.lang.String name, java.lang.String group, java.lang.Class jobClass),该构造函数要求指定Job的实现类,以及任务在Scheduler中的组名和Job名称
JobDetail对象由Quartz客户端在Job被加入到scheduler时创建。它包含了Job的各种设置属性以及一个JobDataMap对象,这个对象被用来存储给定Job类实例的状态信息。Scheduler并不保存真正的Job Class,而是通过JobDetail来保存。
注意:给scheduler传入了一个JobDetail实例,而且这个JobDetail实例只是简单提供了类名来引用被执行的Job。每次scheduler执行这个任务时,它就创建这个类的新实例,然后调用该实例的execute()。Job不必担心线程安全性,因为同一时刻仅有一个线程去执行给定Job类的实例,甚至是并发执行同一Job也是如此
(1)Job类必须有一个无参数的构造函数
(2)Job类中定义的成员数据失去意义,因为这些成员数据值在每次执行的时候被“清空”了
(4)JobBuilder:定义、创建JobDetail实例
(5)Trigger(触发器)
代表一个调度参数的配置,什么时候去调
1.SimpleTrigger:简单触发
SimpleTrigger用来触发只需执行一次或者在给定时间触发并且重复N次且每次执行延迟一定时间的任务
如果想让触发器在2020年4月25日,上午11: 23:54秒执行,然后每个隔10秒钟重复执行一次,并且这样重复5次,那么SimpleTrigger就可以满足要求
2.CronTrigger:表达式触发
如果需要像日历那样按日程来触发任务,而不是像SimpleTrigger那样每隔特定的间隔时间触发,CronTriggers通常比SimpleTrigger更有用
使用CronTrigger,可以指定诸如[每个周五中午]、或者[每个工作日的9:30]或者[从每个周一、周三、周五的上午9:00到上午10:00之间每隔五分钟]这样日程安排
来触发,甚至,像SimpleTrigger样,CronTrigger也有一个startTime以指定日程从什么时候开始,也有一个(可选的)endTime以指定何时日程不再继续
Quartz Cron表达式
支持7个域,分别是秒/分/时/日/月/周/年。年是非必须项
Cron表达式用来配置CronTrigger实例。Corn表达式是一个由7个表达式组成的字符串。每个表达式都描述了一个单独的日程细节。这些表达式用空格分隔
注意:cron表达式中不区分大小写
Cron表达式对特殊字符的大小写不敏感,对代表星期的缩写英文大小写也不敏感
名称 | 是否必须 | 允许值 | 特殊字符 |
---|---|---|---|
秒 | 是 | 0-59 | , - * / |
分 | 是 | 0-59 | , - * / |
时 | 是 | 0-23 | , - * / |
日 | 是 | 1-31 | , - * ? / L W C |
周 | 是 | 1-7 或 SUN-SAT | , - * ? / L C # |
月 | 是 | 1-12 或 JAN-DEC | , - * / |
年 | 否 | 空 或 1970-2099 | , - * / |
星号(*):可用在所有字段中,表示对应时间域的每一个时刻,例如:在分钟字段时,表示“每分钟”
问号(?):该字符只在日期和星期字段中使用,它通常指定为“无意义的值”,相当于点位符;
减号(-):表达一个范围,如在小时字段中使用“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在星期字段中相当于星期日后的第一天
常用案例
表示式 | 描述 |
---|---|
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:15运行 |
0 * 14 * * ? | 每天14点到15点之间每分钟运行一次,开始于14:00,结束于14:59 |
0 0/5 14 * * ? | 每天14点到15点每5分钟运行一次,开始于14:00,结束于14:55 |
0 0/5 14,18 * * ? | 每天14点到15点每5分钟运行一次,此外每天18点到19点每5钟也运行一次 |
0 0-5 14 * * ? | 每天14:00点到14:05,每分钟运行一次 |
0 10,44 14 ? 3 WED | 3月每周三的14:10分到14:44,每分钟运行一次 |
0 15 10 ? * MON-FRI | 每周一,二,三,四,五的10:15分运行 |
(6)TriggerBuilder:定义、创建Trigger实例
(7)Calendar(时间日期包装类)
org.quartz.Calendar和java.util.Calendar不同,它是一些日历特定时间点的集合(可以简单地将org.quartz.Calendar看作java.util.Calendar的集合,代表一个日历时间点)。一个Trigger可以和多个Calendar关联,以便排除或包含某些时间点
假设:安排每周星期一早上10:00执行任务,但是如果碰到法定的节日,任务则不执行,这时就需要在Trigger触发机制的基础上使用 Calendar进行定点排除
针对不同时间段类型,Quartz在org.quartz.impl.calendar包下提供了若干个Calendar 的实现类,如AnnualCalendar、MonthlyCalendar、WeeklyCalendar分别针对每年、每月和每周进行定义
(8)Scheduler(调度器)
代表一个调度容器,一个调度容器中可以注册多个JobDetail和Trigger。当Trigger与JobDetail组合,就可以被Scheduler容器调度了
代表一个Quartz的独立运行容器,Trigger和JobDetail可以注册到Scheduler中,两者在Scheduler中拥有各自的组及名称,组及名称是Scheduler查找定位容器中某一对象的依据,Trigger的组及名称必须唯一,JobDetail的组和名称也必须唯一(但可以和Trigger的组和名称相同,因为它们是不同类型的)。Scheduler定义了多个接口方法,允许外部通过组及名称访问和控制容器中Trigger和JobDetail。
Scheduler可以将Trigger绑定到某一JobDetail中,这样当Trigger触发时,对应的Job就被执行。一个Job可以对应多个Trigger,但一个Trigger只能对应一个Job。可以通过SchedulerFactory创建一个Scheduler实例。 Scheduler拥有一个SchedulerContext,它类似于ServletContext,保存着Scheduler上下文信息,Job和
Trigger都可以访问SchedulerContext内的信息。SchedulerContext内部通过一个Map,以键值对的方式维护这些上下文数据,SchedulerContext为保存和获取数据提供了多个put()和getXxx()的方法。可以通过Scheduler# getContext()获取对应的SchedulerContext实例
Scheduler接口有两个实现类:StdScheduler(标准默认调度器)、RemoteScheduler(远程调度器)
常用方法
方法 | 描述 |
---|---|
shutdown() | 关闭定时任务调度器 |
Date scheduleJob(JobDetail jobDetail,Trigger trigger) | 添加一个定时任务 |
Date rescheduleJob(String triggerName,String groupName, Trigger newTrigger) | 修改一个定时任务,主要是更改trigger |
boolean deleteJob(String jobName,String jobGroup) | 删除一个定时任务,同时也会将于该jobDetail关联的trigger一并删除 |
String[] getJobGroupNames() | 取得所有的jobDetail组 |
String[] getJobNames(String groupName) | 取得某个group下的所有的jobDetail |
JobDetail getJobDetail(String jobName,String jobGroup) | 取得指定的jobDetail |
Trigger[] getTriggersOfJob(String jobName,String groupName) | 取得指定的jobDetail的所有的Trigger |
Trigger getTrigger(String triggerName,String triggerGroup) | 取得指定的Trigger |
(9)ThreadPool(线程池)
Scheduler使用一个线程池作为任务运行的基础设施,任务通过共享线程池中的线程提高运行效率
基本使用
1.导入依赖
<!-- quartz任务调度 -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP-java7</artifactId>
<version>2.4.13</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.7</version>
</dependency>
<!-- c3p0依赖,已导入 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.4</version>
</dependency>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>mchange-commons-java</artifactId>
<version>0.2.15</version>
</dependency>
2.创建一个实现Job类的execute(),存放具体的任务
public class HelloJob implements Job {
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
// 打印当前的执行时间
Date date = new Date();
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("现在的时间是:" + sf.format(date));
// 具体的业务逻辑
System.out.println("Hello Quartz");
}
}
3.使用SimpleTrigger(简单触发器)、Scheduler(调度器)
public class HelloScheduler {
public static void main(String[] args) throws SchedulerException {
//创建一个jobDetail的实例,将该实例与HelloJob Class绑定
JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
.withIdentity("myJob")
.build();
//创建一个Trigger触发器的实例,定义该job立即执行,并且之后每2秒执行一次,一直执行
SimpleTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity("myTrigger")
.startNow().withSchedule(SimpleScheduleBuilder
.simpleSchedule()
.withIntervalInSeconds(2)
.repeatForever())
.build();
//创建schedule实例。调度器
StdSchedulerFactory factory = new StdSchedulerFactory();
Scheduler scheduler = factory.getScheduler();
scheduler.start();
scheduler.scheduleJob(jobDetail,trigger);
}
}
Spring中使用
1.导入依赖
<!-- spring后需要context-support -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>4.3.26.RELEASE</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.7</version>
</dependency>
<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>
2.三种方式
方式一
(1)继承QuartzJobBean,并重写executeInternal(JobExecutionContext context)
//继承QuartzJobBean,并重写executeInternal方法
public class QuartzTask extends QuartzJobBean {
private int timeout;
private static int i = 0;
// 调度工厂实例化后,经过timeout时间开始执行调度
public void setTimeout(int timeout) {
this.timeout = timeout;
}
@Override
protected void executeInternal(JobExecutionContext context)
throws JobExecutionException {
System.out.println("task running..." + ++i + "进行中...");
}
}
(2)配置spring-quratz.xml文件
1.任务调用类;2.任务调用方式;3.任务调用工厂
<!-- 1.配置任务类 -->
<bean id="myJobDetail"
class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<!-- 指定job的名称 -->
<property name="name" value="exampleJob"/>
<!-- 指定具体的job类 -->
<property name="quartzClass" value="com.my.pojo.QuartzTask"/>
<property name="jobDataAsMap">
<map>
<entry key="timeout" value="0" />
</map>
</property>
<!-- 指定job的分组 -->
<property name="group" value="jobs" />
<!-- 必须设置为true,如果为false,当没有活动的触发器与之关联时会在调度器中删除该任务 -->
<property name="durability" value="true" />
<!-- 指定spring容器的key,如果不设定在job中的jobmap中是获取不到spring容器的 -->
<property name="applicationContextJobDataKey" value="applicationContext" />
</bean>
<!-- 2.调度触发器方式 -->
<bean id="cronTriggerBean"
class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail">
<ref bean="myJobDetail" />
</property>
<!-- cron表达式 -->
<!-- 每一分钟执行一次 -->
<property name="cronExpression" value="0 */1 * * * ?" />
</bean>
<!-- 一个job可以有多个trigger -->
<bean id="cronTriggerBeanTwo"
class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="myJobDetailTwo" />
<property name="cronExpression" value="0 */1 * * * ?" />
</bean>
<!-- 3.调度工厂 -->
<bean id="SpringJobSchedulerFactoryBean"
class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="cronTriggerBean" />
<ref bean="cronTriggerBeanTwo" />
</list>
</property>
</bean>
方式二
(1)不需要继承基类,仍然是POJO
public class QuartzJob {
public void work(){
System.out.println("work running...");
}
}
(2)在spring-quratz.xml配置文件中,配置包装类
<!-- 1.包装工作类 -->
<bean id="quartzJob" class="spring.demo.pojo.QuartzJob"></bean>
<!-- 2.调度触发器方式 -->
<bean id="jobTask"
class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<!-- 需要包装的类,即调度类 -->
<property name="targetObject">
<ref bean="quartzJob" />
</property>
<!-- 调用类中的方法 -->
<property name="targetMethod">
<!-- 具体的方法 -->
<value>work</value>
</property>
</bean>
<!-- 3.调度触发器方式 -->
<bean id="cronTriggerBean"
class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail">
<ref bean="jobTask" />
</property>
<!-- cron表达式 -->
<property name="cronExpression">
<value>10,15,20,25,30,35,40,45,50,55 * * * * ?</value>
</property>
</bean>
<!-- 4.调度工厂 -->
<bean id="SpringJobSchedulerFactoryBean"
class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="cronTriggerBean" />
</list>
</property>
</bean>
方式三
(1)配置applicationContext.xml
通过@Scheduled注解的方式实现,需要修改applicationContext.xml三个部分内容
applicationContext.xml中添加task命名空间,然后添加如下代码
<task:annotation-driven/>
(2)使用@Scheduled注解
最后在定时任务上添加上@Scheduled注解即可,一般都采用cronTrigger方式,即@Scheduled(cron=“相应的定时表达式”)
@Service
public class QuartzService {
@Scheduled(cron = "0/2 * * * * *")
public void process() {
System.out.println("job run...");
}
public static void main(String[] args) throws InterruptedException {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("applicationContext.xml");
while (true) {
System.out.println("main running..." + context);
Thread.sleep(10000);
}
}
}
Spring Boot整合Quartz任务调度
1.导入依赖
<!--spring boot集成quartz -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
2.实现QuartzJobBean,提供具体任务(DateTimeJob.java,具体的业务逻辑代码)
public class DateTimeJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext)
throws JobExecutionException {
// 获取JobDetail中关联的数据
String msg = (String) jobExecutionContext.getJobDetail()
.getJobDataMap()
.get("msg");
System.out.println("current time :" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "---" + msg);
}
}
3.@Configuration配置类(QuartzConfig.java,配置触发器、调度器)
@Configuration
public class QuartzConfig {
// 任务描述,具体可执行的程序
@Bean
public JobDetail printTimeJobDetail() {
return JobBuilder.newJob(DateTimeJob.class) // PrintTimeJob业务类
.withIdentity("DateTimeJob") // 可以给该JobDetail起一个id
// 每个JobDetail内都有一个Map,包含了关联到这个Job的数据,在Job类中可以通过context获取
.usingJobData("msg", "Hello Quartz") // 关联键值对
.storeDurably() // 即使没有Trigger关联时,也不需要删除该JobDetail
.build();
}
// 触发器关联调度器
@Bean
public Trigger printTimeJobTrigger() {
// 表达式调度器
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder
.cronSchedule("0/1 * * * * ?");
return TriggerBuilder.newTrigger()
.forJob(printTimeJobDetail()) // 关联上述的JobDetail
.withSchedule(cronScheduleBuilder) // 关联触发器
.withIdentity("quartzTaskService") // 给Trigger(触发器)起个名字
.build();
}
}