基础理论知识
今天给大家介绍一个非常有名的java任务调度框架Quartz,所谓的任务(作业)调度的核心是支持“某个时间”执行“一个计划任务”;例如我们的财务系统,在每月月初、每个季度或者每年,都需要生成上一个月、季度或者去年的财务报表,而这些报表不可能你自己手动的去生成,这个时候就需要使用任务调度来完成。此框架可以使用Cron表达式更为灵活的来控制在某个时间来作业。
其实任务调度框架很多,选择Quartz的原因它是一个开源的企业级任务调度服务,已经被作为任务调度的良好解决方案;而既然是任务调度自然离不开线程Quartz不仅仅提供线程和线程管理这样简单。为确保可伸缩性,Quartz采用了基于多线程的架构,这样就使Quartz可以并发多个任务。并且Quartz依赖一套松耦合的线程池来管理线程环境。
Quartz的使用非常的简单,简单到让人难以相信。Quartz提出了三个核心概念--任务、触发器和调度器。我们只需要按照这三个核心来完成对应的接口实现与配置就可以实现任务调度了。
三个核心介绍
1.任务-顾名思义就是到了某个时间段,我们要执行某个任务。比如生成报表的任务。
2.触发器-既然任务有了,那么什么时间段触发呢?这个时候就需要有一个触发器。例如每个月月初执行生成报表任务,这就是触发器,触发时间是我们定义的触发规则。
3.调度器-我们既然有了任务,也有了触发器,那么我们要怎么将这两个绑定在一起呢?这个时候就需要调度器。我们可以简单的理解成,调度器就是将一个触发器绑定到一个任务上,让触发器被触发后执行对应的任务。注意:调度器是由工厂创建的。
好了基础知识说完了下面我们开始一个简单的实例
1.创建Quartz任务
Quartz任务创建需要实现org.quartz.Job接口,并重写execute(JobExecutionContextcontext)
方法。
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
/** 此类只是用来执行任务代码 */
public class MyJob implements Job
{
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println("这里是我们的任务代码");
}
}
2.创建Quartz任务
虽然我们上面已经写了类实现了Job接口,但是Quartz并不知道我们有这个任务所以我们需要告诉我们的MyJob这个类是一个任务
//2.2.3版本 注册任务 方式
JobDetail job = JobBuilder.newJob(MyJob.class)//设置要执行的任务是哪个类
.withIdentity("helloJob", "group1")//设置任务信息 参数1任务名 参数2为组名
.build();//创建
3.创建触发器
我们使用的触发器主要有两种
1.SimpleTrigger触发器
2.CronTrigger触发器
两种触发器主要区别在于
1.SimpleTrigger触发器
通过设置触发器的属性:开始时间、结束时间、重复次数、重复间隔等。
2.CronTrigger触发器
可以使用cron表达式来更灵活的控制时间,例如每年执行一次、每天几点执行、每月几号执行等。
//Trigger 是触发器接口,要通过TriggerBuilder来实例化一个
TriggerTrigger trigger1 = TriggerBuilder.newTrigger()
//withIdentity(String name, String group) 指定触发器信息 参数1触发器名 参数2触发器组,如果不创建将自动生成
.withIdentity("trigger1", "test")
//withSchedule(ScheduleBuilder schedBuilder) 设置一个实现了ScheduleBuilder接口的触发器
//这里我们演示的是SimpleTrigger触发器,所以要使用SimpleScheduleBuilder.simpleSchedule()方来创建
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
//指定触发间隔,以秒为单位
.withIntervalInSeconds(5)
//设置执行次数
.withRepeatCount(5))
.build();//创建
Trigger trigger2 = TriggerBuilder.newTrigger()
.withIdentity("cron trigger", "test")
.withSchedule(
//这里我们演示的是CronTrigger触发器,所以要使用CronScheduleBuilder.cronSchedule()方来创建
//每5秒执行一次
CronScheduleBuilder.cronSchedule("0/5 * * ? * *")).build();
4.通过SchedulerFactory获取Scheduler调度器
//SchedulerFactory 提供了用于获得客户可用Scheduler的实例。
/**
* SchedulerFactory的一个实现,它执行所有的工作,基于一个Properties文件的内容创建一个QuartzScheduler实例。
* 默认情况下,从“当前工作目录”加载名为“quartz.properties”的属性文件。
* 如果失败,那么将加载位于org / quartz包中的(作为资源)的“quartz.properties”文件。
* 如果您希望使用除这些默认值之外的文件,则必须定义系统属性“org.quartz.properties”以指向所需的文件。
*/
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
try {
/**
* Scheduler是一个调度器
* 一个Scheduler注册JobDetail和Trigger。一旦注册后,Scheduler 负责执行Job,前提是Trigger的预定时间到达。
*/
Scheduler scheduler = schedulerFactory.getScheduler();//调用getScheduler()方法来获取一个Scheduler实例
//使用scheduleJob(JobDetail jobDetail, Trigger trigger) 方法使 任务 & 触发器 发生关联
scheduler.scheduleJob(job, trigger2);
//使用start()方法开启调度 scheduler.start();
//关闭调度//scheduler.shutdown();
} catch (SchedulerException e)
{
e.printStackTrace();
}
经过以上几步我们就配置好了一个任务,系统会每隔5秒输出这里是我们的任务代码下面我门将上述代码写入main()方法进行测试
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
/**
* 这是我的测试任务类
*/
public class TestJob {
public static void main(String[] args) {
//2.2.3版本 注册任务 方式
JobDetail job = JobBuilder.newJob(MyJob.class)
//设置要执行的任务是哪个类
.withIdentity("helloJob", "group1")
//设置任务信息 参数1任务名 参数2为组名
.build();//创建
//Trigger 是触发器接口,要通过TriggerBuilder来实例化一个Trigger
Trigger trigger1 = TriggerBuilder.newTrigger()
//withIdentity(String name, String group) 指定触发器信息 参数1触发器名 参数2触发器组,如果不创建将自动生成
.withIdentity("trigger1", "test") //withSchedule(ScheduleBuilder schedBuilder) 设置一个实现了ScheduleBuilder接口的触发器
//这里我们演示的是SimpleTrigger触发器,所以要使用SimpleScheduleBuilder.simpleSchedule()方来创建
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
//指定触发间隔,以秒为单位
.withIntervalInSeconds(5)
//设置执行次数
.withRepeatCount(5))
.build();//创建
Trigger trigger2 = TriggerBuilder.newTrigger()
.withIdentity("cron trigger", "test")
.withSchedule(
//这里我们演示的是CronTrigger触发器,所以要使用CronScheduleBuilder.cronSchedule()方来创建\
//每5秒执行一次
CronScheduleBuilder.cronSchedule("0/5 * * ? * *")).build();
//SchedulerFactory 提供了用于获得客户可用Scheduler的实例。
/**
* SchedulerFactory的一个实现,它执行所有的工作,基于一个Properties文件的内容创建一个QuartzScheduler实例。
* 默认情况下,从“当前工作目录”加载名为“quartz.properties”的属性文件。
* 如果失败,那么将加载位于org / quartz包中的(作为资源)的“quartz.properties”文件。
* 如果您希望使用除这些默认值之外的文件,则必须定义系统属性“org.quartz.properties”以指向所需的文件。
*/
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
try {
/**
* Scheduler是一个调度器
* 一个Scheduler注册JobDetail和Trigger。一旦注册后,Scheduler 负责执行Job,前提是Trigger的预定时间到达。
*/
Scheduler scheduler = schedulerFactory.getScheduler();//调用getScheduler()方法来获取一个Scheduler实例
//使用scheduleJob(JobDetail jobDetail, Trigger trigger) 方法使 任务 & 触发器 发生关联
scheduler.scheduleJob(job, trigger2);
//使用start()方法开启调度
scheduler.start();
//关闭调度//scheduler.shutdown();
} catch (SchedulerException e)
{
e.printStackTrace();
}
}
}
扯扯细节
上面就是Quartz2.2.3版本的使用方法,每句代码的意思已为读者姥爷奉上。细心的读者姥爷可能会问execute(JobExecutionContext jobExecutionContext)方法中的JobExecutionContext接口是干什么的?有什么用?
下面我们就来扯一扯JobExecutionContext接口包含任务执行的上下文信息,在执行时将其提供给JobDetail实例,并在执行完成后传递给Trigger实例。
此接口只有一个JobExecutionContextImpl实例,而JobExecutionContextImpl实例的getJobDetail()方法可以得到JobDetail与之关联的Job。
再通过getJobDataMap()方法来获取JobDataMap与该相关联Job。我门就可以获取传递过来的信息了。
两个栗子
栗子1
//System.out.println("这里是我们的任务代码");
JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
//JobDataMap类似于Map集合,所以通过 键 来取 值
System.out.println("猴子的姓名:" + jobDataMap.get("name"));
好了取值说完了,现在说说怎么将值给传递过来。上面我们说过jobExecutionContext.getJobDetail()
方法是为了获取得到JobDetail与之关联的Job,也就是说我们需在在创建完Job之后获得JobDataMap对象,然后以键值对的方式来将值保存。
栗子2
JobDetail job = JobBuilder.newJob(MyJob.class)
//设置要执行的任务是哪个类
.withIdentity("helloJob", "group1")//设置任务信息 参数1任务名 参数2为组名
.build();//创建
job.getJobDataMap().put("name", "孙悟空");
上面一部分代码是我们在 main()方法中创建的任务,job.getJobDataMap().put("name", "孙悟空");
用来获取JobDataMap对象后设置键值。
到此Quartz就结束了,这里写的不是很深入希望各位读者姥爷多多谅解。由于我是JavaWeb开发,所以还会写一篇Spring整合Quartz的例子。如果你使用的是低版本,如1.8.6版本请看一下例子。
1.8.6栗子
//参数1任务名 参数2为组名 参数3类字节码
JobDetail job = new JobDetail("helloJob", "group1", MyJob.class);
job.getJobDataMap().put("name", "孙悟空");
//参数1触发器名,参数2组名,参数3执行次数 -1 为无限制,参数4 每个多少秒执行一次
Trigger trigger1 = new SimpleTrigger("trigger1", "group", 5, 5 * 1000);
Trigger trigger2 = null;
try {
//参数1调度器名,参数2组名,参数3Cron表达式
trigger2 = new CronTrigger("cron trigger", "group", "0/5 * * ? * *");
} catch (ParseException e) {
e.printStackTrace();
}
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
try {
Scheduler scheduler = schedulerFactory.getScheduler();
scheduler.scheduleJob(job, trigger2);
scheduler.start();
} catch (SchedulerException e)
{
e.printStackTrace();
}
可以看出SchedulerFactory的方式没有变只是JobDetail和Trigger的创建方式变了。
注意:
实例化SimpleTrigger与CronTrigger的时候组名可以不需要。根据情况而定,我个人认为还是写上。