什么是Quartz
Quartz是一个完全由Java编写的开源调度框架,作用是在Java应用程序中进行作业调度提供简单而强大的机制。Quartz允许开发人员根据时间间隔来调度任务。它实现了作业和出发起的多对多的关系,还能把作业与不同的触发器关联。简单地创建一个org.quartz.Job接口的Java类,Job接口只包含一个方法:
public void execute(JobExecutionContext context) throws JobExecutionException;
在Job接口的实现类里面。添加需要的业务逻辑到execute()方法中去。配置好Job实现类并设定好调度时间表,Quartz就会在设定的时间点调度作业并执行execute()方法。
整合了Quartz的应用程序可以重用不同事件的作业,还可以作为一个时间组合多个作业。Quartz通过属性文件来配置JDBC事务的数据源、全局作业、触发器侦听器、插件、线程池等。
Quartz室友James House创建并最初于2001年春天被加入到sourceforge工程。接下来的几年里,有很多的新特性和版本出现,但是直到项目迁移到显得站点并成为OpenSymphony项目家族的一员,才开始真正启动并收到广泛的关注。
目前的版本已经是2.0以上了,v2.x相对于v1.x有很对新特性出现,并有很多的改动,具体参见Quartz官网上说明。这里介绍的仍然是v1.x(v1.8.6)。
“hello,Quartz"
配置环境:
1.下载Quartz
2.阅读Readme.txt,了解每个Jar包的作用,将Quartz.jar包金额lib/下的几个Jar包,以及相关依赖的Jar包放在工程的classpath中去。
先来看一个简单的Quartz莹莹,让它每个五秒输出”hello,Quartz",一共输出10次。
代码1:创建任务
//引入包
import java.util.Date;
import org.quartz.Job;
import org.quartz.JobExecutionException;
import org.quartz.JobExecutionContetx;
//创建自己的一个继承类
public class HelloQuartzJob extends Job{
//实现其唯一的execute()方法
public void execute(JobExecutionContext context) throws JobExecutionException{
System.out.println("hello,Quartz!---executing its JOB at"+new Date()+"by"+context.getTrigger().getName();
}
}
为了能够执行这个任务,首先我们得得到一个Schedule实例,然后创建一个包含任务信息的JobDetail,最后创建一个Trigger管理任务的执行。
代码2:调度任务
//引入包
import java.sql.Date;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.SimpleTrigger;
import org.quartz.impl.StdSchedulerFactory;
//创建类
public class HelloQuartzScheduling{
public static void mian(String[] args){
//创建调度工厂
SchedulerFactory schedulerFactory=new StdSchedulerFactory();
//创建调度
Scheduler scheduler=schedulerFactory.getScheduler();
//创建任务细节
JobDetail jobDetai=new JobDetail("helloQuartzJob",Scheduler.DEFAULT_GROUP,HelloQuartzJob.class);
//创建简单触发器
SimpleTrigger simpleTrigger=new SimpleTrigger("simpleTrigger",Scheduler.DEFAULT_GROUP);
//设置触发器启动时间
simpleTrigger.setStartTime(new Date(System.currentTimeMillis());
//设置触发器执行间隔(毫秒计)
simpleTrigger.setRepeatInterval(5000);
//设置触发器触发次数
simpleTrigger.setRepeatCount(10);
//设置调度的任务
scheduler.schedulerJob(jobDetail,simpleTrigger);
//启动时刻表
scheduler.start();
}
}
下面是运行的输出情况:
从上图我们可以发现如果设置触发器的触发次数为10次的话,会输出11次,因此实际生产中,我们得把触发次数主动减一次。
Job接口包含了唯一方法execute(),我们需要将任务逻辑添加到里面去。StdSchedulerFactory.getScheduler()的作用是返回一个SchedulerFactory的实例,然后我们在创建JobDetail实例,并且传递三个参数(“任务名”,任务组名,实现任务的类)。第一个参数任务名是用于引用该任务;第二个参数任务组名,这里使用默认名,任务组名主要用来引用集合起来的一组任务,比如说可以使用Scheduler.pauseJobGroup()来暂停一组任务,每个组中的任务名是唯一的;第三个参数时实现任务的类。创建完JobDetail后,我们还得创建一个Trigger,这里使用的SimpleTrigger类,它提供了Java中Timer风格的触发器行为。传递给SimpleTrigger构造方法的两个参数分别是触发器名和任务组名,触发器名在它所在的组必须是唯一的。接下来我们的设置一下触发器的一些属性,setStartTime()是用来设置启动时间的,setRepeatInterval()是用来设置触发间隔的,要注意单位是毫秒,setRepeatCount()是设置触发次数。最后,通过scheduler.start()来启动调度,也可以通过stop()来终止调度。
CronTrigger类
Quartz有两大触发器,除了上面的Simpletrigger外,还有CronTrigger。CronTrigger能够提供对复杂的触发器表达式的支持。CronTrigger时基于Unix Cron守护进程的,它是一个调度程序,支持简单而强大的触发器语法。
使用Cron Trigger主要的是掌握Cron表达式。Cron表达式包含6个必要组件和1个可选组件,如下表所示。
位置 | 含义 | 允许的特殊字符 |
1 | 秒(0-59) | , - * / |
2 | 分(0-59) | , - * / |
3 | 小时(0-24) | , - * / |
4 | 日期(1-31) | , - * / ? L W C |
5 | 月(JAN-DEC或1-12) | , - * / |
6 | 星期(SUN-SAT或1-7) | , - * / ? L C # |
7 | 年(可选,1970-2099),若为空,表示全部时间范围 | , - * / |
特殊字符的含义,见下表。
特殊字符 | 说明 |
* | 通配符,任意值 |
? | 无特定值。通常和其他指定的值一起使用,表示必须显示该值但不能检查 |
- | 范围。e.g.小时域中10-12表示10:00,11:00,12:00 |
, | 列分隔符。可以让你指定一系列的值。e.g.在星期域中指定MON,TUE和WED |
/ | 增量。表示一个值的增量。e.g.分钟域中0/1表示从0开始,每次增加1min |
L | 表示Last。它在日期域和星期域中表示有所不同。在日期域中,表示这个月的最后一天,而在星期域中永远是7(即星期六)。当你希望使用星期域中的某一天时,L字符非常有用。e.g.星期域中6L表示每个月的最后一个星期五 |
W | 在本月内里当天最近的工作日出发,所谓的最近工作日,即当天到工作日的前后最短距离,如果当天即为工作日,则距离为0;所谓本月内是指不能跨月渠道最近工作日,即使前/后月份的最后一天或者第一天满足最近工作日的条件。e.g.LW表示这个月的追后一个工作日触发,W强烈依赖于月份 |
# | 表示该月的第几个星期,e.g.1#2表示每个月第一个星期的星期一 |
C | 日历值。日历值时更具一个给定的日历计算出来的。在日历值域中给定一个20C将在20日(日历包括20日)或20日后日历中包含的第一天(不包括20日)激活触发器。e.g.在星期域中使用6C表示日历中星期五(日历包括星期五)或者第一天(日历不包括星期五) |
Cron表达式举例:
“30 * * * * ?”每半分钟触发任务
“30 10 * * * ?"每小时的10分30秒触发任务
”30 10 1 * * ?“每天的1点10分30秒触发任务
”30 10 1 20 * ?“每月20号的1点10分30秒触发任务
”30 10 1 20 10 ? *“每年的10月20号的1点10分30秒触发任务
”30 10 1 30 10 ? 2011“2011年的10月20号1点10分30秒触发任务
”30 10 1 ? 10 * 2011“2011年的10月每天1点10分30秒触发任务
”30 10 1 ? 10 SUN 2011“2011年的10月的每个星期天的1点10分30秒触发任务
”15,30,45 * * * * ?“每15秒,30秒,45秒触发任务
”15-45 * * * *?“在15到45秒内,每秒都触发任务
”15/5 * * * *?“每分钟的15秒开始触发,每隔5秒触发一次
”15-30/5 * * * * ?“每分钟的15秒到30秒之间触发任务,每隔五秒触发一次
”0 0/3 * * * ?"每小时的0分0秒开始,每隔3分钟触发任务
“0 15 10 ? * MON-FRI"星期一到星期五的10点15分触发任务
”0 15 10 L * ?“每个月的最后一天的10点15分触发任务
”0 15 10 LW * ?"每个月的最后一个工作日的10点5分触发任务
“0 15 10 ? * 5L"每个月的最后一个星期四的10点15分触发任务
”0 15 10 ? * 5#3“每月的第3个星期的星期四的10点15分触发任务
接着,我们可以尝试下将上面的SimpleTrigger的例子改成CronTrigger,代码如下:
//引入包
import java.text.ParseException;
import org.quartz.CronTrigger;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.impl.StdSchedulerFactory;
public class HelloQuartzScheduling{
public static void main(String[] args)throws ParseException,SchedulerException{
//构造调度工厂
SchedulerFactory schedulerFactory=new StdSchedulerFactory();
//创建调度
Scheduler scheduler=schedulerFactory.getScheduler();
//创建任务细节
JobDetail jobDetail=new JobDetail("jobDetail",Scheduler.DEFAULT_GROUP,HelloQuartzJob.class);
//创建Cron触发器触发规则
String cronExpression="30/5 * * * * ?";//每分钟的30秒开始,每个5秒触发一次任务
//创建Cron触发器
CronTrigger cronTrigger=new CronTrigger("cronTrigger",Scheduler.DEFAULT_GROUP,cronExpression);
//在调度中添加任务和触发器
scheduler.scheduleJob(jobDetail,cronTrigger);
//启动触发器
cronTrigger.start();
}
}
输出结果如图所示:
CronTrigger在使用HolidayCalendar类时还可以做到排除某一段时间,比如说国庆节不执行调度任务,代码示例如下:
//引入包
import java.util.Calendar;
import java.text.ParseException;
import java.quartz.CronTrigger;
import java.quartz.JobDetail;
import java.quartz.Scheduler;
import java.quartz.SchedulerException;
import java.quartz.SchedulerFactory;
import java.quartz.impl.StdSchedulerFactory;
import java.quartz.impl.calendar.HolidayCalendar;
public class HelloQuartzScheduling{
public static void main(String[] args)throws ParseException,SchedulerException{
//创建调度工厂
SchedulerFactory schedulerFactory=new StdSchedulerFactory();
//创建调度
Scheduler scheduler=schedulerFactory.getScheduler(0;
//创建任务
JobDetai jobDetail=new JobDetail("jobDetail",Scheduler.DEFAULT_GROUP,HelloQuartzJob.class);
//创建日历
Calendar cal=Calendar.getInstance();
//设置日历属性
cal.set(2012,Calendar.OCTOBER,1);
//创建假期日历
HolidayCalendar holidayCal=new HolidayCalendar();
//排除假期的那一天
holidayCal.addExculdedDate(cal.getTime());
//在调度中去除假期
//addCalendar(String calName,Calendar calendar,boolean replaceboolean updateTrigger)
scheduler.addCalendar("calendar",holidayCal,true,false);
//创建Cron触发器
CronTrigger cronTRigger=new CronTrigger("cronTrigger",Scheduler.DEFAULT_GROUP,cronExpression);
//Cron触发器中设置假期
cronTrigger.setCalendarName("calendar");
//在调度中加入任务及触发器
scheduler.scheduleJob(jobDetail,cronTrigger);
//启动触发器
scheduler.start();
}
}
JobStore:任务持久化
Quartz支持任务持久化,这可以让你在运行时增加任务或者对现存的任务进行修改,并为后续任务的执行持久化这些变更和增加的部分。中心概念时JobStore接口。默认的华师RAMJobStore。
配置文件
上述没有用到任何的配置文件。Quartz支持配置文件,它的好处时比编写代码简单,且以后不需要重新编译源码。
>>配置quartz.properties特性文件
quartz.properties文件定义了Quartz应用运行时的行为,还包含了许多空值Quartz运转的属性。它应该放在工程的classpath中。
代码清单5:quartz.properties
#==========================================================================================
#Configure Main Scheduler Properties
#==========================================================================================
#实例名
org.quartz.scheduler,instanceName=QuartzScheduler
#实例ID
org.quartz.scheduler,instanceId=AUTO
#==========================================================================================
#Configure ThreadPool
#==========================================================================================
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
#线程个数
org.quartz.threadPool.threadCount=3
org.quartz.threadPool.threadPriority=5
#==========================================================================================
#Configure JobStore
#==========================================================================================
org.quartz.jobStore,misfireThreshold=60000
org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore
#==========================================================================================
#Configure Plugins
#==========================================================================================
org.quartz.plugin.triggerstory.class=org.quartz.plugins.history.LoggingJobHistoryPlugin
#org.quartz.plugins.xml.JobInitializationPlugin时Quartz自带的插件
#默认时,这个插件会在classpath中搜索名为quartz_jobs.xml
#的文件,并从中加载Job和Trigger信息
#v1.8之前用JobInitializationPlugin
#org.quartz.plugin.jobInitiaization.class=org.quartz.xml.JobInitializationPlugin
org.quartz.plugin.jobInitializer.class=
org.quartz.plugins.xml.XMLSchedulingDataProcessorPlugin
org.quartz.plugin.jobInitializer.fileNames=quartz_jobs.xml
org.quartz.plugin.jobInitializer,failOnFieldNotFound=true
org.quartz.plugin.jobInitializer.scanInterval=10
org.quartz.plugin.jobInitializer.wrapInUserTransaction=false
#关闭quartz新版本检测功能
org.quartz.scheduler.skipUpdateCheck=true
>>配置quartz_jobs.xml文件
在配置quartz_jobs.xml时,遇到一个问题:
因为quartz从版本1.8开始,配置文件有所改动,以前quartz自带的插件是JobInitializationPlugin,而1.8中的时XMLSchedulingDataProcessorPlugin.xml,schema亦有所改变。难道是改变后的配置不对了?错误提示是插件类找不到,jar包也都加入到了工程里了。最终终于发现,在quartz.properties特性文件中配置查建行最后多打了个分号。原来是一个多余的分号引发的错误。
下面时新的xml配置文件格式实例。
代码清单6:quartz_jobs.xml格式
<?xml version="1.0" encoding="UTF-8"?>
<job-scheduling-data xmlns="http://www.quartz-scheduler.org/xml/JobSchedulingData"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.quartz-scheduler.org/xml/JobSchedulingData http://www.quartz-scheduler.ogr/xml/job_scheduling_data_1_8.xsd"
version="1.8">
<pre-processing-commands>
<!--在执行作业和触发器之前执行的命令-->
<delete-jobs-in-group>*</delete-jobs-in-group>
<!--删除标示组中的所有作业,如果是”*“,则删除所有作业组中的作业,同时也会删除与作业相关的触发器-->
<delete-triggers-in-group>*</delete-triggers-in-group>
<delete-job>
<!--删除指定的作业,同时也会删除与之关联的触发器-->
<name></name>
<group></group>
</delete-job>
<delete-trigger>
<!--指定要删除的触发器-->
<name></name>
<group></group>
</delete-trigger>
</pre-processing-commands>
<prscessing-directives>
<!--在计划作业和触发器上应该遵循的命令和原则-->
<overwrite-existing-data>true or false</overwrite-existing-data>
<!--是否覆写已经存在的任务计划数据,如果为false并且ignore-duplicates非false,那么文件中同名的触发器或作业将会继续存在,则会产生错误-->
<ignore-duplicates>true or false</ignore-duplicates>
<!--如果为true,计划中的任何同名的作业/触发器将会被忽略,不会产生错误-->
</processiong-directives>
<schedule>
<job>
<name>JobName</name>
<group>JobGroup</group>
<description></description>
<job-class></job-class>
<job-listener-ref></job-listener-ref>
<!--volatility,durability,recover必须按顺序设定-->
<volatility></volatility>
<durability></durability>
<recover></recover>
<job-data-map>
<!--entry可以设定多个-->
<entry>
<key></key>
<value></value>
<entry>
</job-data-map>
</job>
<trigger>
<!--Trigger分为simple,cron,date-interval三种类型,一个trigger中只能指定一种类型-->
<simple>
<name></name>
<group></group>
<description></description>
<job-name></job-name>
<job-group></job-group>
<calendar-name></calendar-name>
<volatility></volatility>
<job-data-map>
<entrty>
<key></key>
<value></value>
<entrty>
</job-data-map>
<start-time></start-time>
<end-time></end-time>
<misfire-instruction></misfile-instruction>
<repeat-count></repeat-count>
<repeat-interval></repeat-interval>
</simple>
<cron>
<name></name>
<group></group>
<description></description>
<job-name></job-name>
<job-group></job-group>
<calendar-name></calendar-name>
<volatility></volatility>
<job-data-map>
<entrty>
<key></key>
<value></value>
<entrty>
</job-data-map>
<start-time></start-time>
<end-time></end-time>
<misfire-instruction></misfile-instruction>
<cron-expression></cron-expression>
<time-zone></time-zone>
</cron>
<date-interval>
<name></name>
<group></group>
<description></description>
<job-name></job-name>
<job-group></job-group>
<calendar-name></calendar-name>
<volatility></volatility>
<job-data-map>
<entrty>
<key></key>
<value></value>
<entrty>
</job-data-map>
<start-time></start-time>
<end-time></end-time>
<misfire-instruction></misfile-instruction>
<repeat-interval></repeat-interval>
<repeat-interval-unit></repeat-interval-unit>
</date-interval>
</trigger>
</schedule>
</job-scheduling-data>
代码清单7:quartz_jobs.xml示例
<?xmlversion="1.0"encoding="UTF-8"?>
<job-scheduling-data xmlns="http://www.quartz-scheduler.org/xml/JobSchedulingData"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.quartz-scheduler.org/xml/JobSchedulingData http://www.quartz-scheduler.org/xml/job_scheduling_data_1_8.xsd"
version="1.8">
<pre-processing-commands>
<!--clear all jobs in scheduler-->
<delete-jobs-in-group>*</delete-jobs-in-group>
<!--delete all triggers in scheduler-->
<delete-triggers-in-group>*</delete-triggers-in-group>
</pre-processing-commands>
<processing-directives>
<overwrite-existing-data>true</overwrite-existing-data>
<ignore-duplicates>false</ignore-duplicates>
</processing-directives>
<schedule>
<job>
<name>helloQuartzJob</name>
<group>DEFAULT</group>
<description>简单的quartz使用</description>
<job-class>HelloQuartzJob</job-class>
<volatility>false</volatility>
<durability>true</durability>
<recover>false</recover>
</job>
<trigger>
<cron>
<name>trigger</name>
<group>DEFAULT</group>
<job-name>helloQuartzJob</job-name>
<job-group>DEFAULT</job-group>
<cron-expression>30/5 * * * * ?</cron-expression>
</cron>
</trigger>
<schedule>
</job-scheduling-data>
代码清单8:Quartz任务调度
public class HelloQuartzScheduling{
public static void main(String[] args)throws SchedulerException,ParseException{
//创建调度工厂
SchedulerFactory schedulerFactory=new StdSchedulerFactory();
//创建调度
Scheduler scheduler=schedulerFactory.getScheduler();
//启动调度
scheduler.start();
}
}
然后编译运行,但是可能会报出一个错误:
经检查发现,该异常的出现是缺少jta.jar这个包。只要从网上下载后,然后添加到classpath中去,那么就可以正常编译运行了。