Spring framework(9):Quartz 的基本使用和 Spring 集成
Quartz 的快速使用
JDK 1.3 开始通过 java.util.Timer 和 java.util.TimerTask 提供了简单的任务调度功能,允许用户调度一个按固定时间间隔运行的任务,但是对于复杂任务调度业务实现起来还是很麻烦;
OpenSymphony 提供的 Quartz 开源库在此基础上,提供了复杂灵活的任务调度功能,使用 quertz 需要导入以下依赖:
org.quartz-scheduler:quartz
Quartz 官方技术文档:http://www.quartz-scheduler.org
Quartz 对于任务调度进行了高度抽象,提出了调度器、任务、触发器3个核心的概念,并提供了以下的接口和实现类进行支持:
- Job
用于定义需要执行的任务,JobExecutionContext 类提供了调度上下文的信息,JobDataMap 用于保存 Job 运行时的信息;
- JobDetail
用于描述 Job 的事项类及其相关的静态信息,这是由于 Quartz 每次执行 Job 时,都会重新创建一个新的 Job 实例,因此需要一个类用于反射实例化 Job;
- Trigger
用于描述触发 Job 执行的时间触发规则,主要有以下2个实现类:
- SimpleTrigger:简单触发器,用于仅需要触发一次或以固定时间间隔周期性执行的任务;
- CronTrigger:通过 Cron 表达式定义各种复杂的调度方案;
一个 Trigger 只能对应一个 Job,一个 Job 可以对应多个 Trigger;
- Calendar
用于描述一些特定时间点的集合,可以看成 java.util.Calendar 时间点的集合;一个 Trigger 可以与多个 Calendar 关联,org.quartz.impl.calendar 包下提供了 Calendar 的几个实现类:AnnualCalendar、MonthlyCalendar、WeeklyCalendar 分别针对每年、每月、每周进行定义;
- Scheduler
用于代表一个 Quartz 的独立运行容器,Trigger 和 JobDetail 可以注册到 Schedular 中;
- ThreadPool
Schedular 使用一个线程池作为任务运行的基础设施,任务通过共享线程池中的线程来提高运行效率;
快速使用
以下是 Quartz 的快速使用示例,示例的 quartz 版本为 2.2.3;
创建一个 Job 的实现类, HelloJob
public class HelloJob implements Job{
public void execute(JobExecutionContext context) throws JobExecutionException {
JobDetail jobDetail = context.getJobDetail();
System.out.println("["+jobDetail.getKey().getName() + ","
+jobDetail.getKey().getGroup() +"] "
+"Hello world! "
+ new Date());
}
}
演示SimpleTrigger的使用的示例代码
//通过工厂方法创建Scheduler
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
//创建 JobDetail
JobDetail jobDetail = newJob(HelloJob.class) //指定实现类类型
.withIdentity("job1","group1") //指定identity
.build();
//创建 Trigger
SimpleTrigger simpleTrigger =(SimpleTrigger)newTrigger()
.withIdentity("trigger1","group1") //指定 identity
.startAt(new Date()) //指定启动时间,如果为现在启动,使用.startNow()
.withSchedule(simpleSchedule()
.withIntervalInSeconds(5) //指定循环间隔时间,可以 withIntervalInMinutes,withIntervalInHours
.repeatForever()) //指定循环次数,无限循环,可以.withRepeatCount(200) 指定固定循环次数
.endAt(dateOf(19,0,0)) //停止时间为当天的 19:00:00,可选
.build();
//在 scheduler 绑定 jobDetail 和 trigger
scheduler.scheduleJob(jobDetail,simpleTrigger);
//启动 scheduler
scheduler.start();
演示 CronTrigger 使用的示例代码
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
JobDetail jobDetail = newJob(HelloJob.class)
.withIdentity("job2","group1")
.build();
CronTrigger cronTriggerr = (CronTrigger)newTrigger()
.withIdentity("trigger2","group1")
.withSchedule(cronSchedule("0/5 * * * * ?")) //指定 cron 表达式:每分钟从0s开始,间隔5s执行
.build();
scheduler.scheduleJob(jobDetail,cronTriggerr);
scheduler.start();
Cron 表达式
Quartz 使用的 CronTrigger 使用类似 Linux 的 Cron 表达式定义时间规则,一个典型的 Cron 表达式由6或7个空格分隔的字段组成,如表达式 “0 0 12 * * ?” 表示每一天的12:00 这一个时刻,具体每一段的规则如下:
※注意星期1表示周日,7表示周六;
※Cron表达式对大小写不敏感;
其中个各个特殊符号的含义如下:
符号 | 适用字段 | 说明 |
* | 所有字段 | 表示对应时间区域的每一个时刻。如: * 在分钟字段时,表示“每分钟” |
? | 日期/星期 | 表示占位符,无意义。 因为日期,星期值往往不需要同时指定,只需要指定其中一个,另一个使用 ? 占位即可; |
- | 所有字段 | 表示一个范围。如:小时字段值为“9-12” ,表示“9点到12点”; |
, | 所有字段 | 表示一个列表值。如:月份字段值为“1,4,6” ,表示“1月、4月、6月”; |
/ | 所有字段 | x/y 表示一个等步长序列,x为起始值,y为增量步长值。 如:在分钟字段中 0/15 表示“0,15,30,45分钟”, */y 等同于 0/y |
L | 日期/星期 | 即“Last”,在两个字段的意思不用; 日期字段:表示该月份的最后一天,“* * * L 1 ?” 表示1月最后一天,即1月31日; 星期字段:表示该周六,等于值 7,在该字字段中,NL 可以表示该月的最后的周几,如 “6L” 表示该月的最后一个周五; |
W | 日期 | 对前导日期进行修饰,表示里改日期最近的工作日。如:日期字段中 “15W” 表示离15日最近的工作日,注意该匹配不会跨月; |
LW | 日期 | 表示当月的最后一个工作日; |
# | 星期 | 表示当月的某个工作日。如星期字段字段中 “6#3” 表示当月的第3个周五,“4#5”表示当月的第5个周三,如果当月不存在这个日期,则不触发; |
C | 日期/星期 | 即“Calendar”,表示计划关联的日期,如果日期没有被关联,则相当于日历中的所有日期。 如:在日期字段中 “5C” 表示5日以后的第一天,在星期字段中“1C”表示周日后的第一天; |
使用 Calendar 排除特定日期
在 Quartz 中可以使用 org.quartz.impl.calendar.Calendar
排除一些特殊的日期,如节假日等,一个示例如下:
//以下要执行一个任务,每1小时执行一次,将国庆节10.1-10.7排除在外
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
JobDetail jobDetail = newJob(HelloJob.class)
.withIdentity("job1","group1")
.build();
//设定 Calendar 集合
AnnualCalendar holidays = new AnnualCalendar();
ArrayList<Calendar> nationDayList = new ArrayList<>();
for(int i=1;i<=7;i++){
Calendar nationDay = new GregorianCalendar();
nationDay.add(Calendar.MONTH,10);
nationDay.add(Calendar.DATE,i++);
}
holidays.setDaysExcluded(nationDayList); //设置每年执行集合的排除时间规则
SimpleTrigger simpleTrigger = (SimpleTrigger) newTrigger()
.withIdentity("job3","group1")
.startAt(dateOf(19,5,0,19,1,2017)) //在 2017-1-19 19:05:00 开始执行
.withSchedule(simpleSchedule()
.withIntervalInHours(1)
.repeatForever()
)
.build();
scheduler.scheduleJob(jobDetail,simpleTrigger);
scheduler.start();
修改调度信息的保存策略
Quartz的默认调度信息保存在内存中,可以通过修改配置文件来修改调度信息的保存策略,同时还可以修改一些运行参数;
默认的配置文件位于 Quartz JAR 包下 org.quartz 的 quartz.properties ;如果要覆盖其配置信息,只需要在项目的根目录下创建一个 quartz.properties 即可;
默认的部分配置如下:
# 配置调度器的线程池
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 10
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true
# 配置任务调度现场数据保存机制:直接保存在内存中
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
以下配置将调度信息使用Mysql数据库作为持久化保存策略:
# 配置任务调度现场数据保存机制:使用数据库持久化
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.tablePrefix = QUTZ_
org.quartz.jobStore.dataSource = qzDS
# 定义具体数据源的配置属性,这里使用 Mysql
org.quartz.dataSource.qzDS.driver = com.mysql.jdbc.Driver
org.quartz.dataSource.qzDS.URL = jdbc:mysql://localhost:3306/sampledb
org.quartz.dataSource.qzDS.user = root
org.quartz.dataSource.qzDS.password = 123456
org.quartz.dataSource.qzDS.maxConnections = 30
注意 Quartz 是使用 JDBC 作为持久化层的,项目中需要导入 Mysql-JDBC 驱动依赖;
如果使用数据库保存调度信息,Quartz 支持从数据库中恢复被中断的任务,如下示例恢复定位为 group1.trigger1 的触发器相关的任务:
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
//获取定位为“group1.trigger1”的 Trigger
SimpleTrigger trigger = (SimpleTrigger) scheduler.getTrigger(TriggerKey.triggerKey("trigger1","group1"));
//恢复运行指定的 Trigger
scheduler.rescheduleJob(trigger.getKey(),trigger);
scheduler.start();
在 Spring 中使用 Quartz
Spring 对于 Quartz 的支持主要包括以下2方面:
-
为 Quartz 的重要组件提供更具 Bean 风格的拓展类;
-
为创建 Scheduler 的 BeanFactory 类,方便在 Spring 环境下创建对应的组件对象,并结合 Spring 容器生命周期执行启动和停止工作;
以下示例在 Spring 中使用 Quartz 的过程,同样需要导入相关的依赖;
创建 JobDetail
有2种创建 JobDetail 的方式:
1)通过 JobDetailFactoryBean 注册
1)通过 JobDetailFactoryBean 注册
在 applicationContext.xml 中的配置如下:
<!--封装 JobDetail 方式1:通过 JobDetailFactoryBean 注册-->
<bean id="jobDetail_1" class="org.springframework.scheduling.quartz.JobDetailFactoryBean"
p:name="jobDetail-1"
p:group="group-1"
p:jobClass="site.assad.quartz.MyJob"
p:applicationContextJobDataKey="applicationContext" >
<property name="jobDataAsMap">
<map>
<entry key="message" value="Hello world" />
</map>
</property>
</bean>
编写 Job 实现类 MyJob
package site.assad.quartz;
public class MyJob implements Job{
public void execute(JobExecutionContext context) throws JobExecutionException {
Map dataMap = context.getJobDetail().getJobDataMap(); //获取储存在配置文件中的 DataMap
String message = (String) dataMap.get("message"); //获取key="message"的value
ApplicationContext applicationContext = (ApplicationContext) dataMap.get("applicationContext"); //获取Spring容器中的上下文对象
System.out.println("message: "+message);
}
}
2)通过服务封装
在 applicationContext.xml
中的配置如下:
<!--封装 JobDetail 方式2:通过服务封装-->
<bean id="jobDetail_2" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"
p:name="jobDetail-2"
p:group="group-1"
p:targetObject-ref="myService"
p:targetMethod="doJob"
p:concurrent="false"/>
<bean id="myService" class="site.assad.service.MyService" />
Job 的指定逻辑是编写在服务层的某个服务方法的,MyService
package site.assad.service;
import org.springframework.stereotype.Service;
public class MyService {
public void doJob(){ //被封装的任务方法
System.out.println("in MyService.doJob()");
}
}
创建 Trigger
在 applicationContext.xml
中的配置如下:
<!--装载 SimpleTrigger-->
<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean"
p:name="trigger-1"
p:group="group-1"
p:jobDetail-ref="jobDetail_1"
p:repeatInterval="3000"
p:repeatCount="100" />
<!--装载 CronTrigger-->
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean"
p:name="trigger-2"
p:group="group-1"
p:jobDetail-ref="jobDetail_2"
p:cronExpression="0/5 * * * * ?" />
创建 Scheduler
在 applicationContext.xml
中的配置如下:
<!--装载 Scheduler -->
<bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="simpleTrigger"/>
<ref bean="cronTrigger"/>
</list>
</property>
<!--显式设置配置文件地址-->
<!--<property name="configLocation" value="classpath:quartz.properties" />-->
</bean>
测试代码如下:
public class QuartzTest {
private ApplicationContext ctx ;
public void init(){
ctx = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
}
public void testScheduler1() throws SchedulerException, InterruptedException {
Scheduler scheduler = ctx.getBean("scheduler",Scheduler.class);
scheduler.start();
Thread.sleep(30 * 1000);
}
}
以上的配置方式是在配置文件中直接将所有的 Trigger 装配到 Scheduler 中,实际使用过程中,可以通过获取 scheduler 的实例,根据业务情况很灵活地为其加载相关地 Trigger、JobDetail;