一、概要
OpenSymphony提供强大的开源任务调度框架
官网:http://www.quartz-scheduler.org
纯Java开发
二、特点
1.强大的调度功能
Spring默认的调度框架,Quartz可以灵活的与Spring集成,实现灵活的可配置的调度功能,提供调度运行环境的持久化机制,可以保存调度线程,即使系统因故障关闭,任务调度数据并不会丢失,Timer不会做到。2.灵活的运用方式
可灵活的定义触发器的调度事件表,并可以对触发器和任务关联映射,提供了组建式监听器,各种插件,线程池等功能,支持调度数据多种存储方式3.分布式和集群能力
三、主要用到的设计模式
Builder模式
Factory模式
组建模式
链式写法
四、三个核心概念
调度器
任务
触发器
四、Quartz体系结构
JobDetail
任务的实现类和类的信息
trigger
触发器
SimpleTrigger
类似Timer的时间操作,例如固定频率的时间操作
CronTrigger
每周五执行任务
Scheduler调度器 (start stop pause resume)
五、重要组成
1.Job一个接口,只有一个方法,类似TimerTask下的run方法
2.JobDetail
Quartz在每次执行Job的时候,都重新创建一个Job实例,所以不直接接受一个Job实例,相反接受一个Job实现类
3.JobBuilder
用来定义创建JobDetail的实例
4.JobStore
用来保存Job数据
5.Trigger
SimpleTrigger
CronTrigger
6.TriggerBuilder
定义或创建触发器实例
7.ThreadPool
Quartz有线程池来运行
8.Scheduler
独立运行容器
9.Calender
一个Trigger可以和多个Calender关联,以排除或包含某个时间点
10.监听器
JobListener
TriggerListener
schedulerListeger
六、Quartz使用
1.准备工作
建立Maven项目工程引入Quartz jar包
2.编写一个Quartz项目
pom<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.3</version>
</dependency>
public class HelloJob implements Job{
@Override
public void execute(JobExecutionContext arg0) throws JobExecutionException{
//打印当前的执行时间,格式为2017-02-02 12:00:00
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("Current Exec Time is " + sf.format(Calender.getTime()));
//编写具体的业务逻辑
System.out.println("Hello world");
//这里可以获取到执行时候传递的参数
}
}
public class HelloScheduler{
public static void main(String ... args){
//创建一个JobDetail实例,将该实例与HelloJob Class绑定
JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
.withIdentity("myJob","group1").build();
//创建一个Trigger实例,定义该Job立即执行,并且每隔两秒钟执行一次,知道永远
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("myTrigger","group1")
.startNow().withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(2).repeatForever()).build();
//创建Scheduler实例
SchedulerFactory sfact = new StdSchedulerFactory();
Scheduler scheduler = sfact.getScheduler();
scheduler.start();
//打印当前的执行时间,格式为2017-02-02 12:00:00
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("Current Exec Time is " + sf.format(Calender.getTime()));
scheduler.scheduleJob(jobDetail,trigger);
}
}
七、浅谈Job&JobDetail
浅谈Job定义
实现业务逻辑的任务接口
Job接口非常容易实现,只有一个execute方法,类似TimerTask的run()方法,在里面编写业务逻辑
Job接口源码
package org.quartz;
public interface Job{
public void execute(JobExecutionContext context) throws JobExecutionException;
}
Job实例在Quartz中的生命周期
每次调度器执行Job时候,它在调用execute方法前会创建一个新的Job实例,当调用完成后,关联Job对象实例会被释放,释放的实例会被垃圾回收机制回收
八、浅谈JobDetail
JobDetail为Job实例提供了许多设置属性,以及JobDataMap成员变量属性,它用来存储特定Job实例的状态信息,调度器需要借助JobDetail对象来添加Job实例。System.out.println("jobDetail,s name:" + jobDetail.getKey().getName());
System.out.println("jobDetail,s group:" + jobDetail.getKey().getGroup());
System.out.println("jobDetail,s jobClass:" + jobDetail.getJobClass().getName());
九、重要属性name 任务的名称
group 任务所在的组,默认值为DETAULT
jobClass 任务的实现类
jobDataMap 任务传参
十、浅谈JobExecutionContext
JobExecutionContext是什么?
当Scheduler调用一个Job,就会将JobExecutionContext传递给Job的execute()方法;
Job能通过JobExecutionContext对象访问到Quartz运行时候的环境以及Job本身的明细数据
十一、浅谈JobDataMap
JobDataMap是什么
在进行任务调度时JobDataMap存储在JobExecutionConext中,非常方便获取
JobDataMap可以用来装载任何可以序列化的数据对象,当Job实例对象被执行时这些参数对象会传递给它
JobDataMap实现了JDK的Map接口,并添加了一些非常方便的方法来存取基本数据类型
获取JobDataMap两种方式
1.从Map中直接获取
//jobDetail中传递两个参数
JobDetail jobDetail = JobBuilder.newJob(HelloJob.class).withIdentity("myJob")
.usingJobData("message","hello myJob1").usingJobData("FloatJobValue",3.14F).builder();
//Trigger中传递两个参数
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("myTrigger","group1")
.usingJobData("message","hello myTrigger1").usingJobData("DoubleTriggerValue",2.0D).
.startNow().withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(2).repeatForever()).build();
//在HelloJob中获取JobDetail信息
JobKey key = jobExecutionContext.getJobDetail().getKey();
System.out.println("my job name and group are : " = key.getName() + ":" + key.getGroup());
//在HelloJob中获取Trigger信息
TriggerKey trkey = jobExecutionContext.getTrigger().getKey();
System.out.println("my trigger name and group name are :" + trkey.getName() + ":" + trkey.getGroup());
//在HelloJob中获取
JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
JobDataMap tjobDataMap = jobExecutionContext.getTrigger().getJobDataMap();
String jobMessage = jobDataMap.getString("mesage");
String jobFloatValue=jobDataMap.getFloat("FloatJobValue");
String jobDoubleValue=jobDataMap.getFloat("DoubleTriggerValue");
其中
JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
JobDataMap tjobDataMap = jobExecutionContext.getTrigger().getJobDataMap();
可以修改为
JobDataMap jobDataMap = jobExecutionContext.getMergedJobDataMap();
但是注意如果JobDetail和Trigger有同名的key,Trigger会覆盖JobDetail中同名的key2.Job实现类中添加setter方法对应的JobDataMap的键值(Quartz框架默认的JobFactory实现类在初始化Job实例对象时会自动的调用这些setter方法)
在HelloJob类中添加
private String message;
private Float FloatJobValue;
private Double DoubleTriggerValue;
//省略getter\setter方法
十二、浅谈TriggerTrigger是什么
Quartz中的触发器用来告诉调度程序作业什么时候粗发,即Trigger对象时用来触发执行Job的。
触发器通用属性
JobKey
表示Job实例的标识,触发器被触发时,该指定的Job实例会执行
StartTime
触发器的事件表首次被触发的时间。它的值类型是java.util.Date.
EndTime
指定触发器不再被触发的时间,他的类型是java.util.Date
public class HelloJob implements Job{
@Override
public void execute(JobExecutionContext arg0) throws JobExecutionException{
//打印当前的执行时间,格式为2017-02-02 12:00:00
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("Current Exec Time is " + sf.format(Calender.getTime()));
Trigger currentTrigger = arg0.getTrigger();
System.out.println("startTime is " + currentTrigger.getStartTime());
System.out.println("endTime is " + currentTrigger.getEndTime());
JobKey jobKey = currentTrigger.getJobKey();
System.out.println("job key info ---" + "jobName:"+jobKey.getName() +"jobGroup:" + jobKey.getGroup());
}
}
public class HelloScheduler{
public static void main(String ... args){
//创建一个JobDetail实例,将该实例与HelloJob Class绑定
JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
.withIdentity("myJob","group1").build();
Date date = new Date();
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
date.setTime(date.getTime() + 3000);
Date endDate = new Date();
endDate.setTime(date.getTime() + 6000);
//创建一个Trigger实例,获取距离当前时间3秒后的时间
//距离当前时间6秒后停止执行
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("myTrigger","group1")
.startAt(date).endAt(endDate).withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(2).repeatForever()).build();
//创建Scheduler实例
SchedulerFactory sfact = new StdSchedulerFactory();
Scheduler scheduler = sfact.getScheduler();
scheduler.start();
//打印当前的执行时间,格式为2017-02-02 12:00:00
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("Current Exec Time is " + sf.format(Calender.getTime()));
scheduler.scheduleJob(jobDetail,trigger);
}
}
十三、SimpleTrigger作用
在一个指定时间段内执行一次作业任务或是在执行的时间间隔内多次执行作业任务
public class HelloJob implements Job{
@Override
public void execute(JobExecutionContext arg0) throws JobExecutionException{
//打印当前的执行时间,格式为2017-02-02 12:00:00
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("Current Exec Time is " + sf.format(Calender.getTime()));
//编写具体的业务逻辑
System.out.println("Hello world");
}
}
public class HelloScheduler{
public static void main(String ... args){
//创建一个JobDetail实例,将该实例与HelloJob Class绑定
JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
.withIdentity("myJob","group1").build();
//获取距离4秒钟之后的具体时间
Date date = new Date();
date.setTime(date.getTime()+4000L);
//距离当前时间4秒钟执行,之后每隔两秒钟重复执行一次任务
Date endDate = new Date();
endDate.setTime(endDate.getTime() + 6000L);
SimpleTrigger trigger = (SimpleTrigger)TriggerBuilder.newTrigger().withIdentity("myTrigger","group1")
.startAt(date).withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).withRepeatCount(1))
.endAt(endDate).withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatForever()).build();
//创建Scheduler实例
SchedulerFactory sfact = new StdSchedulerFactory();
Scheduler scheduler = sfact.getScheduler();
scheduler.start();
//打印当前的执行时间,格式为2017-02-02 12:00:00
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("Current Exec Time is " + sf.format(Calender.getTime()));
scheduler.scheduleJob(jobDetail,trigger);
}
}
需要注意重复次数可以为0,正整数或是SimpleTrigger.REPEAT_INDEFINITELY常量值
重复执行间隔必须为0或长整数
一旦被指定了endTime参数,name他会覆盖重复次数参数效果
十四、CronTrigger
作用
基于日历的作业调度器
而不是像SimpleTrigger那样精确指定间隔时间,比SimpleTrigger更常用。
Cron表达式
用于配置CronTrigger实例
是由7个子表达式组成的字符串,描述了时间表的详细信息。
格式:[秒] [分] [小时] [日] [月] [周] [年]
public class HelloJob implements Job{
@Override
public void execute(JobExecutionContext arg0) throws JobExecutionException{
//打印当前的执行时间,格式为2017-02-02 12:00:00
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("Current Exec Time is " + sf.format(Calender.getTime()));
//编写具体的业务逻辑
System.out.println("Hello world");
}
}
public class HelloScheduler{
public static void main(String ... args){
//创建一个JobDetail实例,将该实例与HelloJob Class绑定
JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
.withIdentity("myJob","group1").build();
//每秒钟触发一次任务
//创建一个Trigger实例,定义该Job立即执行,并且每隔两秒钟执行一次,知道永远
CronTrigger trigger = (CronTrigger)TriggerBuilder.newTrigger().withIdentity("myTrigger","group1")
.startNow().withSchedule(CronScheduleBuilder.cronSchedule(* * * * * ?*));
.withIntervalInSeconds(2).repeatForever()).build();
//创建Scheduler实例
SchedulerFactory sfact = new StdSchedulerFactory();
Scheduler scheduler = sfact.getScheduler();
scheduler.start();
//打印当前的执行时间,格式为2017-02-02 12:00:00
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("Current Exec Time is " + sf.format(Calender.getTime()));
scheduler.scheduleJob(jobDetail,trigger);
}
}
, 表示 或
- 表示 至
* 表示 每
/ 表示 每
?表示 不关心 也可以用*
0 0 10,14,16 * * ? 每天上午10点,下午2点,4点
0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时
0 0 12 ? * WED 表示每个星期三中午12点
0 0 12 * * ? 每天12点触发
0 15 10 ? * * 每天10点15分触发
0 15 10 * * ? 每天10点15分触发
0 15 10 * * ? * 每天10点15分触发
0 15 10 * * ? 2005 2005年每天10点15分触发
0 * 14 * * ? 每天下午的 2点到2点59分每分触发
0 0/5 14 * * ? 每天下午的 2点到2点59分(整点开始,每隔5分触发)
0 0/5 14,18 * * ? 每天下午的 2点到2点59分、18点到18点59分(整点开始,每隔5分触发)
0 0-5 14 * * ? 每天下午的 2点到2点05分每分触发
0 10,44 14 ? 3 WED 3月每周三下午的 2点10分和2点44分触发
0 15 10 ? * MON-FRI 从周一到周五每天上午的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 2002-2005 从2002年到2005年每月最后一周的星期五的10点15分触发
0 15 10 ? * 6#3 每月的第三周的星期五开始触发
0 0 12 1/5 * ? 每月的第一个中午开始每隔5天触发一次
0 11 11 11 11 ? 每年的11月11号 11点11分触发(光棍节)
十五、浅谈Scheduler
Scheduler-工厂模式
所有的Scheduler实例应该由SchedulerFactory来创建
SchedulerFactory
StdSchedulerFactory
DirectSchedulerFactory
回顾Quartz三个核心概念
调度器
任务
触发器
Scheduler创建方式
SchedulerFactory sfact = new StdSchedulerFactory();
Scheduler scheduler = sfact.getScheduler();
DirectSchedulerFactory factory = DirectSchedulerFactory.getInstance();
Scheduler scheduler = factory.getScheduler();
StdSchedulerFactory
使用一组参数(java.util.Properties)来创建和初始化Quartz调度器
配置参数一般存储在quartz.properties中
调用getScheduler方法就能创建和初始化调度器对象
Scheduler主要函数
Date scheduleJob(JobDetail jobDetail,Trigger trigger)
void start()
void standby() scheduler暂时挂起
void shutdown()
十六、quartz.properties
文档的位置和加载顺序
默认加载工程下的quartz.properties文件,如果工程中没有就去读quartz jar包里面的quartz.properties文件里面内容
组成部分
调度器属性
org.quartz.scheduler.instanceName属性用来去区分特定的调度器实例,可以按照功能用途来给调度器起名。
org.quartz.scheduler.instanceId属性和前者一样,也允许任何字符串,单这个值必须是在所有调度器实例中是唯一的,
尤其是在一个集群当中,作为集群的唯一key. 假如你想Quartz帮你生成这个值得话,可以设置为AUTO
线程池属性
threadCount 决定有多少个线程,至少为1,多数机器上值高于100就很不适用了。没有默认值,必须设置值
threadPriority 线程优先级,最大值10 最小值为1,正常值为5(默认值)
org.quartz.treadPool.class
作业存储设置
描述了在调度器实例的生命周期中,Job和Trigger信息是如何存储的
插件配置
满足特定需求用到的Quartz插件配置
十七、使用Quartz配置作业
两种方式
MethodInvokingJobDetailFactoryBean
调用myBean的printMessage方法
MyBean
JobDetailFactoryBean
dispatcher-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd"
default-lazy-init="true">
<!-- 通过mvc:resources设置静态资源,这样servlet就会处理这些静态资源,而不通过控制器 -->
<!-- 设置不过滤内容,比如:css,jquery,img 等资源文件 -->
<mvc:resources location="/*.html" mapping="/**.html" />
<mvc:resources location="/css/*" mapping="/css/**" />
<mvc:resources location="/js/*" mapping="/js/**" />
<mvc:resources location="/images/*" mapping="/images/**" />
<!-- 设定消息转换的编码为utf-8防止controller返回中文乱码 -->
<bean
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="messageConverters">
<list>
<bean
class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
</list>
</property>
</bean>
</list>
</property>
</bean>
<!-- 添加注解驱动 -->
<mvc:annotation-driven />
<!-- 默认扫描的包路径 -->
<context:component-scan base-package="com.imooc.springquartz" />
<!-- mvc:view-controller可以在不需要Controller处理request的情况,转向到设置的View -->
<!-- 像下面这样设置,如果请求为/,则不通过controller,而直接解析为/index.jsp -->
<mvc:view-controller path="/" view-name="index" />
<bean class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView"></property>
<!-- 配置jsp路径前缀 -->
<property name="prefix" value="/"></property>
<!-- 配置URl后缀 -->
<property name="suffix" value=".jsp"></property>
</bean>
<bean id="simpleJobDetail"
class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="myBean" />
<property name="targetMethod" value="printMessage" />
</bean>
<bean id="firstComplexJobDetail"
class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="jobClass"
value="com.imooc.springquartz.quartz.FirstScheduledJob" />
<property name="jobDataMap">
<map>
<entry key="anotherBean" value-ref="anotherBean" />
</map>
</property>
<property name="Durability" value="true"/>
</bean>
<!-- 距离当前时间1秒之后执行,之后每隔两秒钟执行一次 -->
<bean id="mySimpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
<property name="jobDetail" ref="simpleJobDetail"/>
<property name="startDelay" value="1000"/>
<property name="repeatInterval" value="2000"/>
</bean>
<!-- 每隔5秒钟执行一次 -->
<bean id="myCronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="firstComplexJobDetail"/>
<property name="cronExpression" value="0/5 * * ? * *"/>
</bean>
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="jobDetails">
<list>
<ref bean="simpleJobDetail"/>
<ref bean="firstComplexJobDetail"/>
</list>
</property>
<property name="triggers">
<list>
<ref bean="mySimpleTrigger"/>
<ref bean="myCronTrigger"/>
</list>
</property>
</bean>
</beans>
package com.imooc.springquartz.quartz;
import org.springframework.stereotype.Component;
@Component("anotherBean")
public class AnotherBean {
public void printAnotherMessage() {
System.out.println("AnotherMessage");
}
}
package com.imooc.springquartz.quartz;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.springframework.stereotype.Component;
@Component("myBean")
public class MyBean {
public void printMessage() {
// 打印当前的执行时间,格式为2017-01-01 00:00:00
Date date = new Date();
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("MyBean Executes!" + sf.format(date));
}
}
package com.imooc.springquartz.quartz;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
public class FirstScheduledJob extends QuartzJobBean{
private AnotherBean anotherBean;
public void setAnotherBean(AnotherBean anotherBean){
this.anotherBean = anotherBean;
}
@Override
protected void executeInternal(JobExecutionContext arg0)
throws JobExecutionException {
Date date = new Date();
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("FirstScheduledJob Executes!" + sf.format(date));
this.anotherBean.printAnotherMessage();
}
}