文章目录
一、什么是Quartz?
Quartz是一个完全由java编写的开源作业调度框架,它可以与J2EE与J2SE应用程序相结合也可以单独使用。Quartz可以用来创建简单或为运行十个,百个,甚至是好几万个Jobs这样复杂的程序。
二、Quartz的体系结构
三、Quartz的基本入门案例
第一步、导入相应的jar包
<!--quartz核心包-->
<!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version>
</dependency>
<!-- 工具包 -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>2.3.2</version>
</dependency>
<!--日志包整合 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>2.0.0-alpha1</version>
</dependency>
<!-- log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
第二步、新建一个任务类(Task/Job),实现Job接口,重写execute方法
public class helloJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
Date date = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String test = dateFormat.format(date);
//工作内容
System.out.println("数据库备份成功,当前时间为:"+test);
}
}
第三步、编写调度类scheduler,编写任务实例和触发器,通过scheduler将触发器与任务绑定。
public class helloScheduler {
public static void main(String[] args) throws SchedulerException {
//调度器(Scheduler),通过工厂模式从工厂中获取调度实例
Scheduler defaultScheduler = StdSchedulerFactory.getDefaultScheduler();
//任务实例(JobDetail)
JobDetail jobDetail = JobBuilder//加载任务类,和具体任务绑定,该任务必须实现Job接口
.newJob(helloJob.class)
.withIdentity("job1", "group1")//参数一是任务名称,唯一实例;参数二是任务组的名称,将任务分组
.build();
//触发器
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1")//参数一是触发器名称,参数二是触发器组,将触发器分组
.startNow()//马上启动触发器
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).repeatForever()).build();//每1秒触发一次
//scheduler关联任务实例和触发器
defaultScheduler.scheduleJob(jobDetail, trigger);
//启动
defaultScheduler.start();
}
}
四、Job和JobDetail介绍
创建新的Job实例演示:
我们只需要在构造函数中输出一个字符串就好了,如下图所示:
结果:
这就说明在每次调度执行Job的时候,都会实例化一个Job类。
此外,JobDetail还有四个比较重要的属性:
- name:
- group
- jobClass
- jobDataMap
具体的作用看下图
输出结果:
五、JobExecutionContext介绍
Job能通过JobExecutionContext上下文对象访问到Quartz运行时候的环境以及Job本身的详细数据。
public class helloJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
Date date = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String test = dateFormat.format(date);
//获取JobDetail的内容
JobKey jobKey = jobExecutionContext.getJobDetail().getKey();
System.out.println("工作任务名称:"+jobKey.getName()+"工作任务组:"+jobKey.getGroup());
System.out.println("任务类的名称(带路径):"+ jobExecutionContext.getJobDetail().getJobClass().getName());
System.out.println("任务类的名称:"+ jobExecutionContext.getJobDetail().getJobClass().getSimpleName());
//工作内容
System.out.println("数据库备份成功,当前时间为:"+test);
}
}
运行结果:
工作任务名称:job1工作任务组:group1
任务类的名称(带路径):com.gdpu.quartz.job.helloJob
任务类的名称:helloJob
数据库备份成功,当前时间为:2020-11-30 10:45:45
六、JobDataMap介绍
通过JobDataMap,我们可以获取JobDetail,Trigger在scheduler中传递的参数,返回给任务类Job接收,具体的操作如下:
scheduler类:
public class helloScheduler {
public static void main(String[] args) throws SchedulerException {
//调度器(Scheduler),通过工厂模式从工厂中获取调度实例
Scheduler defaultScheduler = StdSchedulerFactory.getDefaultScheduler();
//任务实例(JobDetail)
JobDetail jobDetail = JobBuilder//加载任务类,和具体任务绑定,该任务必须实现Job接口
.newJob(helloJob.class)
.withIdentity("job1", "group1")//参数一是任务名称,唯一实例;参数二是任务组的名称,将任务分组
.usingJobData("message","任务实例传递参数")//传递参数给Job任务类
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1")//参数一是触发器名称,参数二是触发器组,将触发器分组
.startNow()//马上启动触发器
.usingJobData("message","触发器传递参数")//传递参数给Job任务类
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).repeatForever()).build();//每1秒触发一次
//scheduler关联任务实例和触发器
defaultScheduler.scheduleJob(jobDetail, trigger);
//启动
defaultScheduler.start();
}
}
Job任务类:
public class helloJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
Date date = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String test = dateFormat.format(date);
//获取Scheduler中JobDetail,trigger传递的参数
//获取JobDetail中传递的参数
JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
String message = jobDataMap.getString("message");
System.out.println("JobDetail中的参数:"+message);
//获取Trigger中传递的参数
JobDataMap jobDataMap1 = jobExecutionContext.getTrigger().getJobDataMap();
String message1 = jobDataMap1.getString("message");
System.out.println("Trigger中的参数:"+message1);
//工作内容
System.out.println("数据库备份成功,当前时间为:"+test);
}
}
同理,想要获取Trigger的内容的操作,也是同获取JobDetail的操作是类似的,包括获取Trigger的任务名组名等,这里就不展示了。通过:
TriggerKey key= jobExecutionContext.getTrigger().getKey();
即可。
获取其他内容,如任务的当前执行时间以及下次执行时间:
最后总结一点,如果我们觉得先通过Context上下文对象获取jobData,然后再获取JobMap这些方式很麻烦,如下展示的是从JobDetail获取dataMap,Trigger中获取的形式是一样的:
我们也可以在Job类定义一个相关类型的成员变量,例如我在Scheduler中JobDetail通过dataMap穿的value是String类型的,我在Job类就可以定义一个String类型的成员变量去接收,重写set方法即可。
但是这个方法如果遇到同名的key,则Trigger中的value会覆盖JobDetail中的value。
七、有状态的Job和无状态的Job
什么是有状态的Job?
有状态的Job得意思其实就是每次调用Job的时候,只会有JobDataMap这一个实例Map,而无状态的Job在每次调用Job的时候都会新建一个新的JobDataMap,就会导致一些状态信息无法储存共享,其实这就有点像我们Bean的单例和多例模式,演示如下:
//任务实例(JobDetail)
JobDetail jobDetail = JobBuilder//加载任务类,和具体任务绑定,该任务必须实现Job接口
.newJob(helloJob.class)
.withIdentity("job1", "group1")//参数一是任务名称,唯一实例;参数二是任务组的名称,将任务分组
.usingJobData("count",0)//把数值传给Job接收
.build();
//Job任务类(Job)
//演示无状态的Job
++count;//通过set方法直接获取到JobDetail传递过来的count数值,做加法运算后输出
System.out.println("当前的count数值为:"+count);
jobDataMap.put("count",count);//将新增的数值增加后返回给JobDetail任务实例
//展示结果
当前的count数值为:1
数据库备份成功,当前时间为:2020-11-30 11:34:01
当前的count数值为:1
数据库备份成功,当前时间为:2020-11-30 11:34:02
当前的count数值为:1
数据库备份成功,当前时间为:2020-11-30 11:34:03
我们可以很明显的看到,结果一直都是1,因为这个时候我们的Job是无状态的,每一次DataMap都是一个新的实例。我们可以通过在Job类上加上这样一个注解即可保证DataMap一直都是同一个实例。
@PersistJobDataAfterExecution
加上这个注解之后,我们的Job就变成有状态的了。
八、Trigger介绍
Trigger的相关实现类:
1、SimpleTrigger
通过simpleTrigger可以简单的设置一些设置,如开始时间,结束时间,执行次数,执行间隔等,从而完成在规定的一个时间段执行一些具体的任务,如:
Job类:
public class testSimpleTrigger implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
Date date = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String test = dateFormat.format(date);
//获取trigger
Trigger trigger = context.getTrigger();
//获取trigger实例,一个JobKey对应一个trigger实例
TriggerKey key = trigger.getKey();
System.out.println("任务开始时间为:"+dateFormat.format(trigger.getStartTime()));
System.out.println("任务结束时间为:"+dateFormat.format(trigger.getEndTime()));
System.out.println("Trigger名称为:"+key.getName()+"------------"+"Trigger组名称为:"+key.getGroup());
//工作内容
System.out.println("数据库备份成功,当前时间为:"+test);
}
}
scheduler类:
public class simpleTriggerScheduler {
public static void main(String[] args) throws SchedulerException {
//调度器(Scheduler),通过工厂模式从工厂中获取调度实例
Scheduler defaultScheduler = StdSchedulerFactory.getDefaultScheduler();
//设置任务开始时间
Date startDate = new Date();
startDate.setTime(startDate.getTime()+3000);//延迟三秒后开始任务
//设置任务结束时间
Date endDate = new Date();
endDate.setTime(endDate.getTime()+10000);//任务的结束时间延迟10秒
//任务实例(JobDetail)
JobDetail jobDetail = JobBuilder
.newJob(testSimpleTrigger.class)
.withIdentity("job1", "group1")
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1")
.startNow()//马上启动触发器
.startAt(startDate)
.endAt(endDate)
//withRepeatCount表示执行次数,值得注意的是,当我们规定了指定的执行时间区间
//如果在时间区间结束了,执行任务次数没达到规定的五次,按照执行时间区间来算而不是按照执行次数来算
//表示每隔五秒执行一次,需要执行五次,但由于时间规定了只有十秒,因此最终只执行了两次
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).withRepeatCount(5)).build();
//scheduler关联任务实例和触发器
defaultScheduler.scheduleJob(jobDetail, trigger);
//启动
defaultScheduler.start();
}
}
输出如下:
任务开始时间为:2020-11-30 14:09:10
任务结束时间为:2020-11-30 14:09:17
Trigger名称为:trigger1------------Trigger组名称为:group1
数据库备份成功,当前时间为:2020-11-30 14:09:10
任务开始时间为:2020-11-30 14:09:10
任务结束时间为:2020-11-30 14:09:17
Trigger名称为:trigger1------------Trigger组名称为:group1
数据库备份成功,当前时间为:2020-11-30 14:09:15
2、CronTrigger
如果你要通过像日历那样按日程来触发任务,可以基于CronTrigger来进行任务调度。如每个工作日的上午9点,或者每隔一三五的上午9点-12点,诸如此类。
Cron表达式:
注意两点的是:
一、周中SAT表示的是7,SUN表示的是一。而非我们传统的周一到周日对应的1-7。
二、周和日字段不会同时存在,例如我们制定了日这个字段为每个月的15号,我们不能保证每个月的15号都是周一或者周二。同理,如果我们指定了每周一或者周二,日这个字段也不需要了,我们可以通过?来表示我不需要这个字段。
Cron特殊字符相关解释:
具体示例:
写在最后,当然我们也可以不用自己手写,我们可以直接通过网上的一些在线工具直接生成Cron表达式:Cron表达式在线生成
九、配置、资源SchedulerFactory(了解)
public class testScheduler implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
Date date = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String test = dateFormat.format(date);
//工作内容
System.out.println("数据库备份成功,当前时间为:"+test);
}
}
public class testJobScheduler {
public static void main(String[] args) throws SchedulerException, InterruptedException {
//通过子类的方式获取对象
StdSchedulerFactory factory = new StdSchedulerFactory();
Scheduler scheduler = factory.getScheduler();
JobDetail jobDetail = JobBuilder
.newJob(testScheduler.class)
.withIdentity("job1", "group1")
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1")
.startNow()//马上启动触发器
.withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ?"))
.build();
//scheduler关联任务实例和触发器
Date date = scheduler.scheduleJob(jobDetail, trigger);
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("scheduler开始的时间是"+dateFormat.format(date));
//启动
scheduler.start();
//Scheduler执行两秒后挂起
Thread.sleep(2000L);
//挂起,暂停任务
scheduler.standby();
//中止,杀死任务后不允许重启,有一个布尔类型的参数,true会等待所有任务执行完毕,而false则会直接关闭scheduler
scheduler.shutdown();
//scheduler执行5秒后自动开启
Thread.sleep(5000L);
scheduler.start();
}
}
十、Quartz.Properties
默认的Quartz.Properties,如果我们不想用这个文件,我们也可以在资源类目录下新建一个同名文件进行覆盖。
org.quartz.scheduler.instanceName: DefaultQuartzScheduler
org.quartz.scheduler.rmi.export: false
org.quartz.scheduler.rmi.proxy: false
org.quartz.scheduler.wrapJobExecutionInUserTransaction: false
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.misfireThreshold: 60000
org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore
十一、Quartz监听器(重要)
在任务调度中,监听你想关注的任务。主要的监听器有三种,分别为:
TriggerListener
JobListener
SchedulerListener
在这之前得先了解全局监听器和非全局监听器的区别:
全局监听器:能够接收所有Job/Listener的通知
非全局监听器:只能接收在其注册上的Job或者Trigger
1、JobListener
JobListener的四种方法:
JobListener的使用方法:
第一步、编写监听类,实现JobListener接口,重写四种监听方法:
//实现JobListener接口
public class MyJobListener implements JobListener {
@Override
public String getName() {
String name = this.getClass().getName();
System.out.println("监听器的名称是:"+name);
return name;
}
//前置方法
@Override
public void jobToBeExecuted(JobExecutionContext context) {
String name = context.getJobDetail().getKey().getName();
System.out.println("Job的名称是"+name+ "Scheduler在JobDetails即将执行时调用的方法");
}
@Override
public void jobExecutionVetoed(JobExecutionContext context) {
String name = context.getJobDetail().getKey().getName();
System.out.println("job的名称是:"+name+"Scheduler在JobDetails即将执行时,但又被TriggerListener否决时调用的方法");
}
//后置方法
@Override
public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
String name = context.getJobDetail().getKey().getName();
System.out.println("job的名称是:"+name+"Scheduler在JobDetails执行之后调用的方法");
}
}
第二步、注册监听器即可
public class JobListenerScheduler {
public static void main(String[] args) throws SchedulerException {
//调度器(Scheduler),通过工厂模式从工厂中获取调度实例
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
//任务实例(JobDetail)
JobDetail jobDetail = JobBuilder
.newJob(HelloJobListener.class)
.withIdentity("job1", "group1")
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1")
.startNow()//马上启动触发器
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).withRepeatCount(3)).build();
//scheduler关联任务实例和触发器
scheduler.scheduleJob(jobDetail, trigger);
//注册局部的JobListener,表示指定的任务job
//scheduler.getListenerManager().addJobListener(new MyJobListener(), KeyMatcher.keyEquals(JobKey.jobKey("job1","group1")));
//注册全局的JobListener
//scheduler.getListenerManager().addJobListener(new MyJobListener(),EverythingMatcher.allJobs());
//启动
scheduler.start();
}
}
2、TriggerListener
TriggerListener的五种方法:
public class MyTriggerListener implements TriggerListener {
private String name;
public MyTriggerListener(String name) {
this.name = name;
}
@Override
public String getName() {
System.out.println("监听器的名称是:"+name);
return name;
}
@Override
public void triggerFired(Trigger trigger, JobExecutionContext context) {
String name = trigger.getKey().getName();
System.out.println(name+"触发器被触发");
}
@Override
public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
String name = trigger.getKey().getName();
System.out.println(name+"触发器没被触发");
return true;//不会执行job方法,也就是说我的Job类中的数据库备份语句不会被输出
}
@Override
public void triggerMisfired(Trigger trigger) {
String name = trigger.getKey().getName();
System.out.println(name+"错过触发");
}
@Override
public void triggerComplete(Trigger trigger, JobExecutionContext context, Trigger.CompletedExecutionInstruction triggerInstructionCode) {
String name = trigger.getKey().getName();
System.out.println(name+"完成之后触发");
}
}
public class TriggerListenerScheduler {
public static void main(String[] args) throws SchedulerException {
//调度器(Scheduler),通过工厂模式从工厂中获取调度实例
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
//任务实例(JobDetail)
JobDetail jobDetail = JobBuilder
.newJob(HelloJobListener.class)
.withIdentity("job1", "group1")
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1")
.startNow()//马上启动触发器
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).withRepeatCount(3)).build();
//scheduler关联任务实例和触发器
scheduler.scheduleJob(jobDetail, trigger);
//注册局部的JobListener,表示指定的任务job
//scheduler.getListenerManager().addTriggerListener(new MyTriggerListener("simple"), KeyMatcher.keyEquals(TriggerKey.triggerKey("trigger1","group1")));
//注册全局的TriggerListener
//scheduler.getListenerManager().addTriggerListener(new MyTriggerListener("simple"), EverythingMatcher.allTriggers());
//启动
scheduler.start();
}
}
3、SchedulerListener
与Scheduler有关的事件,增加删除一个Trigger/Job,scheduler发生错误,关闭scheduler等。
相关方法: