1. Quartz的几个常用API
1.1 Scheduler——与调度程序交互的主程序接口
Scheduler调度程序任务执行计划表,只有安排进行执行计划的任务job(通过scheduler.schedulerjob方法安排进行执行计划),当它预先定义的执行时间到了的时候(任务触发trigger),该任务才会执行。
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
1.2 Job
1.2.1 介绍
我们预先定义的希望在未来时间能被调度程序执行的任务类,可以自定义。
job是工作任务调度的接口,任务类需要实现该接口,该接口中定义execute方法,类似JDK提供的TimeTask类的run方法,在里面编写任务执行的业务逻辑。
例如:
public class HelloJob implements Job {
@Override
public void execute(JobExecutionContext arg0) throws JobExecutionException {
Calendar calendar = Calendar.getInstance();
Date time = calendar.getTime();
String format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(time);
System.out.println("任务开始执行,时间是:" + format);
}
}
1.2.2 Job实例在Quartz中的生命周期
每次调度器执行job的时候,它在调用execute方法前会创建一个新的job实例,当调用完成后,关联的Job对象实例会被释放,释放的实例会被垃圾回收机制回收。
例如:我们可以在Job中的构造函数中打印一句话来验证该生命周期
package cn.bjc.quartz.job;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public class HelloJob implements Job {
public HelloJob() {
super();
System.out.println("hello Job");
}
@Override
public void execute(JobExecutionContext arg0) throws JobExecutionException {
Calendar calendar = Calendar.getInstance();
Date time = calendar.getTime();
String format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(time);
System.out.println("任务开始执行,时间是:" + format);
}
}
运行结果如图:
我们发现,每次执行构造函数都会打印一句话,这说明在调用execute方法前会创建一个新的job实例
1.2.3 有状态的Job和无状态的Job
无状态的Job:默认的Job状态,每次调用时都会创建一个新的JobDataMap,这个有点类似Struts2中的值栈。
例如:我们在jobDetail中传入参数count,默认值为0,作为计数器,然后在job中打印每次运行之后的count值
HelloJob.java
package cn.bjc.quartz.job;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.TriggerKey;
public class HelloJob implements Job {
// 定义计数器
private Integer count;
public void setCount(Integer count) {
this.count = count;
}
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println("count = " + (++count)); // 计数器+1,并且打印出来
// 将计数器放回到JobDetail的JobDataMap中
context.getJobDetail().getJobDataMap().put("count", count);
Calendar calendar = Calendar.getInstance();
Date time = calendar.getTime();
String format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(time);
System.out.println("任务开始执行,时间是:" + format);
}
}
HelloSchedulerDemo.java
package cn.bjc.quartz.main;
import org.quartz.JobBuilder;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.SimpleTrigger;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;
import cn.bjc.quartz.job.HelloJob;
public class HelloSchedulerDemo {
public static void main(String[] args) throws Exception {
// 1. 调度器(Scheduler),从工厂获取
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); // 内部默认是new StdSchedulerFactory()
// 2. 任务实例(JobDetail),通过JobBuilder创建
JobDetail jobDetail = JobBuilder.newJob(HelloJob.class) // 加载任务类,与HelloJob绑定
.withIdentity("job1", "group1") // 参数一:任务的名称(唯一实例);参数二:任务组的名称,对任务进行分组
.usingJobData("msg", "日志打印") // 传递参数到job
.usingJobData("count", 0) // 传入计数器,默认值为0
.build();
// 3. 触发器(Trigger)
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1") // 参数一:触发器的名称;参数二:触发器组的名称,对触发器进行分组
.startNow() // 启动时间,这里设置马上启动
.withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatSecondlyForever(5))// 每5秒重复执行
.usingJobData("msg", "simpleTrigger") // 传递参数到job
.build();
// 最后,让调度器关联任务和触发器,保证按照触发器定义的条件去执行任务。
scheduler.scheduleJob(jobDetail, trigger);
// 启动
scheduler.start();
// 关闭
// scheduler.shutdown();
}
}
运行结果:
发现,三次执行的结果,count值均为1。这个就说明了,在默认情况下是无状态的Job且每次调用时都会创建一个新的JobDataMap。
有状态的Job:可以理解为多次job调用期间可以持有一些状态信息,这些状态信息存储在JobDataMap中。
要实现有状态的job,只需要在job类上加上注解@PersistJobDataAfterExecution即可
例如:
package cn.bjc.quartz.job;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.PersistJobDataAfterExecution;
import org.quartz.TriggerKey;
@PersistJobDataAfterExecution
public class HelloJob implements Job {
// 定义计数器
private Integer count;
public void setCount(Integer count) {
this.count = count;
}
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println("count = " + (++count)); // 计数器+1,并且打印出来
// 将计数器放回到JobDetail的JobDataMap中
context.getJobDetail().getJobDataMap().put("count", count);
Calendar calendar = Calendar.getInstance();
Date time = calendar.getTime();
String format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(time);
System.out.println("任务开始执行,时间是:" + format);
}
}
运行结果:
很明显,计数器在随着调度程序的执行而累加。
1.3 JobBuilder
用于声明一个任务实例,也可以定义关于该任务的详情,比如任务名、组名等。这个声明的实例将会作为一个实际执行的任务。
1.4 JobDetail——任务实例
使用JobDetail来定义定时任务的实例,JobDetail实例是通过JobBuilder类创建的。
JobDetail为job实例提供了许多设置属性,以及JobDataMap成员变量属性,它用来存储特定Job实例的状态信息,调度器需要借助JobDetail对象来添加Job实例。
例如:
JobDetail jobDetail = JobBuilder.newJob(HelloJob.class) // 加载任务类,与HelloJob绑定
.withIdentity("job1", "group1") // 参数一:任务的名称(唯一实例);参数二:任务组的名称,对任务进行分组
.build();
注意:这里如果没有指定组名,默认的组名是" DEFAULT "
JobDetail的重要属性:name、group、jobClass、jobDataMap
例如:
System.out.println("任务名称:" + jobDetail.getKey().getName());
System.out.println("任务所属的组:" + jobDetail.getKey().getGroup());
System.out.println("任务类:" + jobDetail.getJobClass().getName());
jobDetail还可以携带参数到Job实例,通过usingJobData方法
例如:
JobDetail jobDetail = JobBuilder.newJob(HelloJob.class) // 加载任务类,与HelloJob绑定
.withIdentity("job1", "group1") // 参数一:任务的名称(唯一实例);参数二:任务组的名称,对任务进行分组
.usingJobData("msg", "日志打印") // 传递参数到job
.build();
1.5 JobExecutionContext
当scheduler调用一个job,就会将JobExecutionContext传递给Job的execute()方法,job能通过JobExecutionContext对象访问到Quartz运行时候的环境以及job本身的明细数据。
1.5.1 获取JobDetail的内容
public void execute(JobExecutionContext context) throws JobExecutionException {
// 获取JobDetail的内容
JobKey key = context.getJobDetail().getKey();
System.out.println("工作任务名称:" + key.getName()); //job1
System.out.println("工作任务的组:" + key.getGroup()); // group1
System.out.println("任务类:" + context.getJobDetail().getJobClass().getName()); // cn.bjc.quartz.job.HelloJob
// 获取JobDataMap
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
System.out.println(jobDataMap.getString("msg"));
}
1.5.2 获取Trigger的内容
JobDataMap jobDataMap2 = context.getTrigger().getJobDataMap();
System.out.println(jobDataMap2.getString("msg"));
TriggerKey key = context.getTrigger().getKey();
System.out.println(key.getName());
System.out.println(key.getGroup());
1.5.3 获取其他内容
// 任务本次运行时间
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(context.getFireTime()));
// 任务下一次运行时间
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(context.getNextFireTime()));
1.6 JobDataMap
JobDataMap可以包含不限量的(序列化)数据对象,在job实例执行的时候,可以使用其中的数据。
1.6.1 使用Map获取
1. 在进行任务调度时,JobDataMap存储在JobExecutionContext中,非常方便获取。
2. JobDataMap可以用来装载任何可序列化的数据对象,当job实例对象被执行时,这些参数对象会传递给它。
3. JobDataMap实现了java.util.Map接口,并且添加了非常方便的方法用来存取基本数据类型
例如:获取trigger中的JobDataMap储存的msg数据
JobDataMap jobDataMap2 = context.getTrigger().getJobDataMap();
System.out.println(jobDataMap2.getString("msg"));
注意:要获取其数据,需要在执行的时候通过usingJobData传递参数
例如:
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1") // 参数一:触发器的名称;参数二:触发器组的名称,对触发器进行分组
.startNow() // 启动时间,这里设置马上启动
.withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatSecondlyForever(5))// 每5秒重复执行
.usingJobData("msg", "simpleTrigger") // 传递参数到job
.build();
1.6.2 setter方法
Job实现类中添加setter方法对应JobDataMap的键值,Quartz框架默认的JobFactory实现类在初始化Job实例对象时会自动的调用这些setter方法。
例如:
package cn.bjc.quartz.job;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public class HelloJob implements Job {
private String msg;
public void setMsg(String msg) {
this.msg = msg;
}
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println(msg);
Calendar calendar = Calendar.getInstance();
Date time = calendar.getTime();
String format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(time);
System.out.println("任务开始执行,时间是:" + format);
}
}
注意:在使用setter方法的时候,如果遇到同名的key,Trigger中的.usingJobData会覆盖JobDetail中的.usingJobData
1.7 TriggerBuilder
用于创建触发器Trigger实例
相关的API
1. newTrigger():创建一个TriggerBuilder实例
TriggerBuilder triggerBuilder = TriggerBuilder.newTrigger();
2. withIdentity:给TriggerBuilder实例设置身份标志
triggerBuilder .withIdentity("trigger1", "group1");
3. startNow():马上执行
triggerBuilder.startNow()
3. startAt(Date startTime):设置任务的开始时间
triggerBuilder.startAt(new Date())
4. endAt(Date endTime):设置任务的结束时间
triggerBuilder.endAt(new Date())
5. withSchedule(ScheduleBuilder scheduleBuilder):设置任务触发条件
// 设置任务每5秒执行一次
triggerBuilder.withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatSecondlyForever(5));
6. build():创建触发器
例如:
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1") // 参数一:触发器的名称;参数二:触发器组的名称,对触发器进行分组
.startNow() // 启动时间,马上启动
.withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatSecondlyForever(5))// 每5秒重复执行
.build();
1.8 Trigger——触发器
Trigger对象是用来触发执行job的,当调度一个job的时候,我们实例一个触发器然后调整它的属性来满足job执行的条件。表明任务在什么时候会执行,定义了一个已经安排的任务将会在什么时候执行的时间条件。例如:每2s执行一次
Trigger trigger = newTrigger()
.withIdentity("trigger1", "group1")
.startNow()
.withSchedule(simpleSchedule()
.withIntervalInSeconds(40)
.repeatForever())
.build();
1.8.1 Trigger介绍
Quartz有一些不同的触发器类型,不过,用的最多的是SimpleTrigger和CronTrigger,其实例是通过TriggerBuilder来创建的
1.8.2 Trigger下的相关的概念
1. JobKey:表示job实例的标志,触发器被触发时,该指定的job实例会被执行。
// 获取trigger的jobkey
JobKey jobKey = context.getTrigger().getJobKey();
System.out.println("trigger的名称:" + jobKey.getName());
System.out.println("trigger的组:" + jobKey.getGroup());
2. startTime:表示触发器的时间表,第一次开始被触发的时间,其数据类型是java.util.Date
3. endTime:指定触发器终止被处罚的时间,它的数据类型也是java.util.Date
Trigger trigger = context.getTrigger();
Date startTime = trigger.getStartTime();
Date endTime = trigger.getEndTime();
1.8.3 SimpleTrigger触发器
SimpleTrigger对于设置和使用是最为简单的一种QuartzTrigger。它是为那种需要在特定的日期/时间启动,且以一个可能的时间间隔重复执行N次的Job所设计的。
API
1. SimpleScheduleBuilder.repeatSecondlyForever(5):每5秒执行一次
2. SimpleScheduleBuilder.withRepeatCount(2):重复执行3次
例如:任务每5秒执行一次,重复执行3次
SimpleScheduleBuilder.repeatSecondlyForever(5).withRepeatCount(2)
1.8.4 CronTrigger触发器——基于日历的作业调度器
如果你需要像日历那样按日程来触发任务,而不是想SimpleTrigger那样每隔特定的时间间隔触发,CronTrigger通常比SimpleTrigger更有用,因为它是基于日历的作业调度器。
使用CronTrigger,你可以指定诸如“每个周五中午”,或者“每个工作日9:30”或者“从周一、周三、周五的上午9:00到上午10:00之间每隔5分钟”这样日程来安排触发。
甚至像SimpleTrigger一样,CronTrigger也有一个startTime以指定日程从什么时候开始,也有一个(可选的)endTime以指定日程不再继续。
1. cron表达式
cron表达式被用来配置CronTrigger实例,cron表达式是一个由7个子表达式组成的字符串,每个子表达式都描述了一个单独的日程细节,这些子表达式用空格分隔,分别表示
1. Seconds 秒
2. Minutes 分
3. Hours 时
4. Day-of-Month 月中的天
5. Month 月
6. Day-of-Week 周中的天
7. Year(optional field) 年 (可选的域)
参数取值如下表:
字段 | 是否必填 | 允许值 | 运行的特殊字符 |
秒 | 是 | 0-59 | , - * / |
分 | 是 | 0-59 | , - * / |
小时 | 是 | 0-23 | , - * / |
日 | 是 | 1-31 | , - * / ? L W C |
月 | 是 | 1-12 或者JAN-DEC | , - * / |
周 | 是 | 1-7或者SUN-SAT | , - * / ? L C # |
年 | 否 | 不填写,或者1970-2099 | , - * / |
单个表达式可以包含范围或者列表。
例如:周中的天可以表示为
"MON-FRI" :周一到周五
"MON,WED,FRI" :周一、周三、周五
"MON-WED,SAT":周一到周三和周六
特殊字符的用法
符号 | 含义 |
* | 表示域中的每个可能的值。例如:Month域中的*表示每个月,在Day-of-Week中的*表示周中的每一天 |
? | 表示不指定值,使用的 场景为不需要关心当前设置这个字段的值。因为月份中的日期与星期中的日期,这两个元素是互斥的, 因此应该通过设置一个问号(?)来表明你不想设置的那个字段 |
- | 表示区间,例如:在小时上设置10-12,表示10,11,12点都会触发 |
, | 表示指定多个值。例如:在周字段上设置“MON,WED,FRI”,表示周一、周三、周五触发 |
/ | 表示值的增量。例如:如果分钟域中放入‘ 0/15 ’,它表示每个15分钟,从0开始,如果在分钟域中使用' 3/20 '表示小时中每隔20分钟,从第3分钟开始或者另外相同的形式就是' 3,23,43 ' |
L | 可以在Day-of-Month与Day-of-Week中使用,表示last,但是在这两个域中的意义不同。 例如: 1. 在day-of-month中,L表示这个月的最后一天 2. 在day-of-week中,L表示7或者“ SAT ”,但是如果在day-of-week域中,L跟在别的值后面,则表示“当月的最后的周xxx”。例如:6L或者FRIL都表示本月的最后一个周五。 注意:在使用L选项的时候,最重要的是不要指定列表或者值范围,否则可能会导致混乱 |
W | 用来指定给定目标最接近的周几(在day-of-week中指定)。例如:如果你为day-of-month域指定为15W,则表示距离月中15号最近的周几 |
# | 表示月中的第几个周几。例如:day-of-week域中6#3或者FRI#3表示月中的第三个周五 |
注意:
1. ?的用法,如果指定了日,那么周就不用指定了,如果指定了周那么日就不用指定了。原因很好理解。
2. L和W可以一起使用(企业中可用在工资计算)
3. #可表示月中第几个周几(企业中用于计算母亲节父亲节)
4. 周字段英文字母不区分大小写,例如:MON=mon
2. 几个cron表达式
表达式 | 含义 |
0 0 10,14,16 * * ? | 每天上午10点,下午12点,下午四点 |
0 0/30 9-17 * * ? | 朝九晚五工作时间内每半小时,从0分开始每隔30分钟发送一次 |
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/55 14 * * ? | 在每天下午2点到下午2:55期间,从0开始到55分钟触发 |
0 0/55 14,18 * * ? | 在每天下午2点到下午2:55期间和下午6点带6:55期间,从0开始到55分钟触发 |
0 0-5 14 * * ? | 在每天下午2点到下午2:05期间每1分钟 |
0 10,44 14 ? 3 WED | 每年三月星期三的下午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 | 200-2005年的每月的最后一个星期五上午10:15触发 |
0 15 10 ? * 6#3 | 每月的第三个星期五上午10:15触发 |
例如:
JobTriggerCron.java
package cn.bjc.quartz.job;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobKey;
import org.quartz.PersistJobDataAfterExecution;
import org.quartz.Trigger;
import org.quartz.TriggerKey;
public class JobTriggerCron implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
Calendar calendar = Calendar.getInstance();
Date time = calendar.getTime();
String format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(time);
System.out.println("任务开始执行,时间是:" + format);
}
}
JobTriggerCronMain.java
package cn.bjc.quartz.main;
import java.util.Date;
import org.quartz.CronScheduleBuilder;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;
import cn.bjc.quartz.job.JobTriggerCron;
public class JobTriggerCronMain {
public static void main(String[] args) throws Exception {
Date startDate = new Date();
startDate.setTime(startDate.getTime() + 3000);
Date endDate = new Date();
endDate.setTime(endDate.getTime() + 10000);
// 1. 调度器
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// 2. 任务实例
JobDetail jobDetail = JobBuilder.newJob(JobTriggerCron.class)
.withIdentity("job1", "group1") // 参数一:任务的名称(唯一实例);参数二:任务组的名称,对任务进行分组
.build();
// 3. 触发器(Trigger)
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1") // 参数一:触发器的名称;参数二:触发器组的名称,对触发器进行分组
//.startAt(startDate)
//.endAt(endDate)
.startNow()
.withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * * * ?"))// 日历每5秒执行
.build();
// 最后,让调度器关联任务和触发器,保证按照触发器定义的条件去执行任务。 SimpleScheduleBuilder
scheduler.scheduleJob(jobDetail, trigger);
// 启动
scheduler.start();
}
}
运行结果: