Quartz 任务调度框架
一、背景和简介
1、产生背景
从JDK1.3开始,Java通过java.util.Timer和java.util.TimerTask可以实现定时器。为什么要使用Quartz而不是使用Java中的这些标准功能呢?
主要原因如下:
- Timers没有持久化机制
- Timers不灵活 (只可以设置开始时间和重复间隔,不是基于时间、日期、天等(秒、分、时)的)
- Timers 不能利用线程池,一个timer一个线程
- Timers没有真正的管理计划
2、简介
Quartz是一个完全由Java编写的开源任务调度的框架,通过触发器设置作业定时运行规则,控制作业的运行时间。其中quartz集群通过故障切换和负载平衡的功能,能给调度器带来高可用性和伸缩性。主要用来执行定时任务,如:定时发送信息、定时生成报表等等。
优势:
- Quartz是非常灵活的,并包含多个使用范例,它们可以单独或一起使用,以实现您所期望的行为,并使您能够以最“自然”的方式来编写您的项目的代码。
- Quartz是非常轻量级的,只需要非常少的配置 —— 它实际上可以被跳出框架来使用,如果你的需求是一些相对基本的简单的需求的话。
- Quartz具有容错机制,并且可以在重启服务的时候持久化(”记忆”)你的定时任务,你的任务也不会丢失
二、构成和使用
1、Quartz的三大组件
Quartz框架主要核心组件包括调度器、触发器、作业。调度器作为作业的总指挥,触发器作为作业的操作者,作业为应用的功能模块。
- 简介
- Job为作业的接口,为任务调度的对象 。
- JobDetail用来描述Job的实现类及其它相关的静态信息 ,用于包装业务代码为一个可执行的job。
- Trigger做为作业的定时管理工具,一个Trigger只能对应一个作业实例,而一个作业实例可对应多个触发器 。
- scheduler做为定时任务容器,是quartz最上层的东西,它提携了所有触发器和作业,使它们协调工作,每个Scheduler都存有JobDetail和Trigger的注册,一个Scheduler中可以注册多个JobDetail和多个Trigger 。
2、组件使用
- Job
Job是一个接口,只有一个方法void execute(JobExecutionContext context),被调度的作业(类)需实现该接口中execute()方法,JobExecutionContext类提供了调度上下文的各种信息。每次执行该Job均重新创建一个Job实例 。
- JobDetail
Quartz在每次执行Job时,都重新创建一个Job实例,所以它不直接接受一个Job的实例,相反它接收一个Job实现类,以便运行时通过newInstance()的反射机制实例化Job。因此需要通过一个类来描述Job的实现类及其它相关的静态信息,如Job名字、描述、关联监听器等信息,JobDetail承担了这一角色。JobDetail 用来保存我们作业的详细信息。一个JobDetail可以有多个Trigger,但是一个Trigger只能对应一个JobDetail 。
- Trigger
Trigger是一个类,描述触发Job执行的时间触发规则。主要有SimpleTrigger和 CronTrigger这两个子类。当仅需触发一次或者以固定时间间隔周期执行,SimpleTrigger是最适合的选择;而CronTrigger则 可以通过Cron表达式定义出各种复杂时间规则的调度方案:如每早晨9:00执行,周一、周三、周五下午5:00执行等 。
- Scheduler
Scheduler负责管理Quartz的运行环境,Quartz它是基于多线程架构的,它启动的时候会初始化一套线程,这套线程会用来执行一些预置的作业。Trigger和JobDetail可以注册到Scheduler中;Scheduler可以将Trigger绑定到某一JobDetail中,这样当Trigger触发时,对应的Job就被执行。 Scheduler拥有一个SchedulerContext,它类似于ServletContext,保存着Scheduler上下文信息,Job和Trigger都可以访问SchedulerContext内的信息。Scheduler使用一个线程池作为任务运行的基础设施,任务通过共享线程池中的线程提高运行效率。
三、原理和实现
1、开发步骤:
- 创建Job,被执行的内容。必须有一个实现了Job接口的类作为参数,实现该接口就是为了后面调用其实现的execute()方法。和线程的run方法类似(恰巧,线程实现Runnable接口,也叫任务task);
- 创建trigger。时间触发了事情的执行;
- 创建sheduler。一定要有谁来安排这么一个事情的执行;
- 组装上面的三个核心组件,运行代码。
2、代码示例
导入依赖
<!-- 定时任务 -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
创建job
package com.cg.job;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 定时执行任务的任务类-测试
* @author wanghao
*
*/
public class TestJob01 implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
// 业务逻辑
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
System.out.println("任务打印"+sdf.format(new Date()));
}
}
创建任务调度器(以simpleTrigger方式触发)
package com.cg.job;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
/**
* 任务调度类
* 三个元素:任务类,触发器,调度器
*/
public class TestJob01Scheduler {
//创建调度器
public static Scheduler getScheduler() throws SchedulerException {
//通过工厂类获取实例
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
return schedulerFactory.getScheduler();
}
// 执行任务
public static void run() throws SchedulerException{
/*
* 创建任务
* 包装业务代码为一个可执行的job
* 反射机制
* */
JobDetail jobDetail = JobBuilder.newJob(TestJob01.class).withIdentity("testJob01", "group11").build();
/*
* 创建触发器 每5秒钟执行一次(每五秒钟就触发一次)
*/
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group21")
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever())
.build();
Scheduler scheduler = getScheduler();
//将任务及其触发器放入调度器
scheduler.scheduleJob(jobDetail, trigger);
//调度器开始调度任务
// 启动
if (!scheduler.isShutdown()) {
scheduler.start();
}
}
public static void main(String[] args) throws SchedulerException {
TestJob01Scheduler testJob01Scheduler = new TestJob01Scheduler();
testJob01Scheduler.run();
}
}
创建任务调度器(以CronTrigger方式触发)
package com.cg.job;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
public class QuartzManager {
private static SchedulerFactory gSchedulerFactory = new StdSchedulerFactory();
private static String JOB_GROUP_NAME = "EXTJWEB_JOBGROUP_NAME";
private static String TRIGGER_GROUP_NAME = "EXTJWEB_TRIGGERGROUP_NAME";
/**
* 添加一个定时任务,使用默认的任务组名,触发器名,触发器组名
* @param jobName 任务名
* @param cls 任务
* @param time 时间设置
*/
public static void addJob(String jobName, Class cls, String time) {
try {
// org.quartz-scheduler
Scheduler sched = gSchedulerFactory.getScheduler();
// 任务名,任务组,任务执行类
//创建任务
JobDetail jobDetail = JobBuilder.newJob(cls).withIdentity(jobName, TRIGGER_GROUP_NAME).build();
//可以传递参数
jobDetail.getJobDataMap().put("param", "railsboy");
// 触发器
//每秒钟触发一次任务
//秒 分 时 日 月 年 星期 *=每 /=间隔 */2==每两秒 ,不连贯的值 -连贯的范围
// ?用在星期或者日期上,二者存在冲突,解决二者冲突 L用在星期或者日期上,表示最后一天
// w离该日最近的一个工作日,不可跨月 lw当月最后一个工作日
// #用在星期上,4#2 表示第二个周三
CronTrigger trigger = TriggerBuilder.newTrigger().
withIdentity(jobName, TRIGGER_GROUP_NAME).
// 每三秒执行一次
withSchedule(CronScheduleBuilder.cronSchedule("*/3 * * * * ? *")).
build();
// 触发器时间设定
sched.scheduleJob(jobDetail, trigger);
// 启动
if (!sched.isShutdown()) {
sched.start();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
QuartzManager.addJob("Test01",TestJob01.class,"");
}
}
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在星期字段中相当于星期日后的第一天。
Cron表达式对特殊字符的大小写不敏感,对代表星期的缩写英文大小写也不敏感。