Quartz学习总结之核心接口Scheduler、Job
参考文章:https://www.cnblogs.com/mengrennwpu/p/7141986.html
核心接口如下:
接口 | 含义 |
Scheduler | scheduler的主要API接口 |
Job | 任务实现接口,期望调度器能够执行 |
JobDetail | 用于定义Job实例 |
Trigger | 调度器基于特定时间来执行指定任务的组件 |
JobBuilder | 用于定义、创建JobDetail实例 |
TriggerBuilder | 用于定义、创建Trigger实例 |
1、Scheduler
调度器Scheduler就相当于一个容器,装载着任务和触发器。该类是一个接口,代表一个 Quartz 的独立运行容器, Trigger 和 JobDetail 可以注册到 Scheduler 中, 两者在 Scheduler 中拥有各自的组及名称, 组及名称是 Scheduler 查找定位容器中某一对象的依据, Trigger 的组及名称必须唯一, JobDetail 的组和名称也必须唯一(但可以和 Trigger 的组和名称相同,因为它们是不同类型的)。Scheduler 定义了多个接口方法, 允许外部通过组及名称访问和控制容器中 Trigger 和 JobDetail。
Scheduler 可以将 Trigger 绑定到某一 JobDetail 中, 这样当 Trigger 触发时, 对应的 Job 就被执行。一个 Job 可以对应多个 Trigger, 但一个 Trigger 只能对应一个 Job。可以通过 SchedulerFactory 创建一个 Scheduler 实例。Scheduler 拥有一个 SchedulerContext,保存着 Scheduler 上下文信息,Job 和 Trigger 都可以访问 SchedulerContext 内的信息。SchedulerContext 内部通过一个 Map,以键值对的方式维护这些上下文数据,SchedulerContext 为保存和获取数据提供了个 put()
和 getXxx()
的方法。可以通过 Scheduler.getContext()
获取对应的 SchedulerContext 实例。
一个调度器的生命周期为通过SchedulerFactory创建,直到执行其shutdown()方法。当Scheduler创建之后,可以进行增加、删除及显示任务Job与触发器Trigger,并且执行其他的调度相关的操作,如暂停一个触发器Trigger。需要注意的是,直到调用start()方法时,Scheduler才正式开始执行job和trigger。
StdSchedulerFactory用于创建Scheduler,其依赖于一系列的属性来决定如何产生Scheduler。可以通过四种途径向StdSchedulerFactory提供属性配置信息。
1.1 通过java.util.Properties初始化StdSchedulerFactory
在Quzrtz学习总结一之快速体验的工程基础上,创建一个包com.zdw.zixue.scheduler,该包用来测试通过四种方式来初始化StdSchedulerFactory对象
package com.zdw.zixue.scheduler;import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.impl.StdSchedulerFactory;import java.util.Properties;public class SchedulerDemo1 {public static void main(String[] args) {//创建StdSchedulerFactory实例StdSchedulerFactory stdSchedulerFactory = new StdSchedulerFactory();//创建配置工厂的属性对象Properties properties = new Properties();properties.put(StdSchedulerFactory.PROP_THREAD_POOL_CLASS,"org.quartz.simpl.SimpleThreadPool");//定义线程池properties.put("org.quartz.threadPool.threadCount","10");//默认Scheduler的线程数try {//使用属性对象初始化工厂stdSchedulerFactory.initialize(properties);//创建调度容器SchedulerScheduler scheduler = stdSchedulerFactory.getScheduler();//启动调度容器scheduler.start();System.out.println("调度容器的元数据:"+scheduler.getMetaData());} catch (SchedulerException e) {e.printStackTrace();}}
}
执行,控制台打印:
调度容器的元数据:Quartz Scheduler (v2.2.3) 'QuartzScheduler' with instanceId 'NON_CLUSTERED'Scheduler class: 'org.quartz.impl.StdScheduler' - running locally.Running since: Wed Sep 25 16:07:27 CST 2019Not currently in standby mode.Number of jobs executed: 0Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 10 threads.Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.
可以看到对应的配置属性已经生效。
通过Properties设置工厂属性的缺点在用硬编码,假如需要修改例子中线程数量,将不得不修改代码,然后重新编译。后面几种方法可以解决硬编码的问题。
1.2 通过外部属性文件初始化StdSchedulerFactory
假设在D盘的根目录下有一个文件:properties.properties,(文件名随意),里面的内容如下:
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount=5
package com.zdw.zixue.scheduler;import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.impl.StdSchedulerFactory;public class SchedulerDemo2 {public static void main(String[] args) {//创建StdSchedulerFactory实例StdSchedulerFactory stdSchedulerFactory = new StdSchedulerFactory();try {//使用外部属性文件初始化工厂stdSchedulerFactory.initialize("d:/properties.properties");//创建调度容器SchedulerScheduler scheduler = stdSchedulerFactory.getScheduler();//启动调度容器scheduler.start();System.out.println("2--调度容器的元数据:"+scheduler.getMetaData());} catch (SchedulerException e) {e.printStackTrace();}}
}
执行结果:
2--调度容器的元数据:Quartz Scheduler (v2.2.3) 'QuartzScheduler' with instanceId 'NON_CLUSTERED'Scheduler class: 'org.quartz.impl.StdScheduler' - running locally.Running since: Wed Sep 25 16:11:48 CST 2019Not currently in standby mode.Number of jobs executed: 0Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 5 threads.Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.
1.3 通过含有属性文件内容的java.io.InputStream初始化StdSchedulerFactory
假设在D盘的根目录下有一个文件:properties.properties,(文件名随意),里面的内容如下:
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount=5
package com.zdw.zixue.scheduler;import org.quartz.Scheduler;
import org.quartz.impl.StdSchedulerFactory;import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;public class SchedulerDemo3 {public static void main(String[] args) {//创建StdSchedulerFactory实例StdSchedulerFactory stdSchedulerFactory = new StdSchedulerFactory();try {//得到D盘下的配置文件properties.properties的字节流InputStream inputStream = new FileInputStream(new File("d:/properties.properties"));//使用含有属性文件内容的java.io.InputStream初始化工厂stdSchedulerFactory.initialize(inputStream);//创建调度容器SchedulerScheduler scheduler = stdSchedulerFactory.getScheduler();//启动调度容器scheduler.start();System.out.println("3---调度容器的元数据:"+scheduler.getMetaData());} catch (Exception e) {e.printStackTrace();}}
}
执行结果:
3--调度容器的元数据:Quartz Scheduler (v2.2.3) 'QuartzScheduler' with instanceId 'NON_CLUSTERED'Scheduler class: 'org.quartz.impl.StdScheduler' - running locally.Running since: Wed Sep 25 16:25:49 CST 2019Not currently in standby mode.Number of jobs executed: 0Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 5 threads.Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.
1.4 通过quartz.properties配置文件初始化StdSchedulerFactory
这是推荐使用的方式,也是开发中经常使用的方式。
如果调用无参的initialize方法,StdSchedulerFactory会试图从quartz.properties的文件中加载。quartz.properties相关配置后续文章会介绍,注意quartz.properties的加载顺序为:
a. 检查System.getProperty("org.quartz.properties")中是否设置其他属性文件名
b. 如果a未设置,则将会从当前工作目录中加载quartz.properties配置文件
c. 如果b未找到,则试图从系统的classpath中加载该配置文件。
Scheduler在生命周期中也可执行其他操作,如查询、设置standby模式、继续执行、停止执行。standby模式会导致Scheduler暂时停止查找Job去执行。standby模式的设置直接使用scheudler.standby()即可。
Scheduler的停止方法为shutdown()方法,也可以使用有参shutdown(false),其中参数表示是否让当前正在进行的job正常执行完成才停止Scheduler。
本次,我们在工程的resources目录下面创建quartz.properties文件,内容如下:
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount=8
package com.zdw.zixue.scheduler;import org.quartz.Scheduler;
import org.quartz.impl.StdSchedulerFactory;public class SchedulerDemo4 {public static void main(String[] args) {//创建StdSchedulerFactory实例StdSchedulerFactory stdSchedulerFactory = new StdSchedulerFactory();try {//使用classpath下的quartz.properties配置文件初始化工厂stdSchedulerFactory.initialize();//创建调度容器SchedulerScheduler scheduler = stdSchedulerFactory.getScheduler();//启动调度容器scheduler.start();System.out.println("4--调度容器的元数据:"+scheduler.getMetaData());} catch (Exception e) {e.printStackTrace();}}
}
执行结果:
4--调度容器的元数据:Quartz Scheduler (v2.2.3) 'QuartzScheduler' with instanceId 'NON_CLUSTERED'Scheduler class: 'org.quartz.impl.StdScheduler' - running locally.Running since: Wed Sep 25 16:35:30 CST 2019Not currently in standby mode.Number of jobs executed: 0Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 8 threads.Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.
2、Job
定义需要执行的任务。该类是一个接口,只定义一个方法 execute(JobExecutionContext context)
,在实现类的 execute
方法中编写所需要定时执行的 Job(任务), JobExecutionContext
类提供了调度应用的一些信息,JobExecutionContext对象让Job能访问Quartz运行时环境的所有信息和Job本身的明细数据。运行时环境信息包括注册到Scheduler上与该Job相关联的JobDetail和Trigger。
修改Quzrtz学习总结一之快速体验的工程中的FirstJob.java文件,如下:
package com.zdw.zixue.job;import org.quartz.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;/*** Job实现类,任意java类实现Job接口就行了*/
public class FirstJob implements Job {private static Logger logger = LoggerFactory.getLogger(FirstJob.class);//日志对象@Overridepublic void execute(JobExecutionContext context) throws JobExecutionException {System.out.println("----------Hello World--------------");// 每一个Job都有其自己所属的JobDetailJobDetail jobDetail = context.getJobDetail();//JobDetail的名称和组名System.out.println("JobDetail的名称和组名:"+jobDetail.getKey());// 获取SchedulerScheduler scheduler = context.getScheduler();try {System.out.println("调度器Scheduler name: "+scheduler.getSchedulerName());} catch (SchedulerException e) {e.printStackTrace();}// 具体job的类全名System.out.println("job的类全名:"+jobDetail.getJobClass());// 本次任务执行时间System.out.println("本次任务执行时间:"+context.getFireTime());// 下次任务执行时间System.out.println("下次任务执行时间:"+context.getNextFireTime());// 获取jobDetail的参数信息System.out.println("参数信息:"+jobDetail.getJobDataMap().get("message"));}
}
再次运行FirstJobTest,结果:
----------创建调度任务成功----------
----------Hello World--------------
JobDetail的名称和组名:first_group.first_job
调度器Scheduler name: QuartzScheduler
job的类全名:class com.zdw.zixue.job.FirstJob
本次任务执行时间:Wed Sep 25 16:49:57 CST 2019
下次任务执行时间:Wed Sep 25 16:50:07 CST 2019
参数信息:null
----------Hello World--------------
JobDetail的名称和组名:first_group.first_job
调度器Scheduler name: QuartzScheduler
job的类全名:class com.zdw.zixue.job.FirstJob
本次任务执行时间:Wed Sep 25 16:50:07 CST 2019
下次任务执行时间:Wed Sep 25 16:50:17 CST 2019
参数信息:null
----------Hello World--------------
JobDetail的名称和组名:first_group.first_job
调度器Scheduler name: QuartzScheduler
job的类全名:class com.zdw.zixue.job.FirstJob
本次任务执行时间:Wed Sep 25 16:50:17 CST 2019
下次任务执行时间:Wed Sep 25 16:50:27 CST 2019
参数信息:null
----------Hello World--------------
JobDetail的名称和组名:first_group.first_job
调度器Scheduler name: QuartzScheduler
job的类全名:class com.zdw.zixue.job.FirstJob
本次任务执行时间:Wed Sep 25 16:50:27 CST 2019
下次任务执行时间:Wed Sep 25 16:50:37 CST 2019
参数信息:null
---------------关闭了调度器----------------
3、JobDetail
描述 Job 的实现类及其它相关的静态信息,如:Job 名字、描述、关联监听器等信息。Quartz 每次调度 Job 时, 都重新创建一个 Job 实例, 所以它不直接接受一个 Job 的实例,相反它接收一个 Job 实现类,以便运行时通过 newInstance()
的反射机制实例化 Job。
3.1 Job简述
JobDetail是作为Job实例进行定义的,注意部署在Scheduler上的每一个Job只创建一个JobDetail实例。且需要注意的是注册到Scheduler上的不是Job对象,而是JobDetail实例。
Job 的实例要到该执行它们的时候才会实例化出来。每次 Job 被执行,一个新的 Job 实例会被创建。其中暗含的意思就是你的 Job 不必担心线程安全性,因为同一时刻仅有一个线程去执行给定 Job 类的实例,甚至是并发执行同一 Job 也是如此。
可以使用JobDataMap来定义Job的状态,JobDataMap中可以存入key-value对,这些数据可以在Job实现类中进行传递和访问。这是向你的Job传送配置信息的便捷方法。
Job 能通过 JobExecutionContext 对象访问 JobDataMap
修改Quzrtz学习总结一之快速体验的工程中的FirstJobTest.java文件,把睡眠时间修改为20s,并且把类QuartzServer中的createJobDetail方法修改为:
//创建JobDetailpublic static JobDetail createJobDetail(){JobDetail jobDetail = JobBuilder.newJob(FirstJob.class)//设置执行任务的类.withIdentity("first_job", "first_group")//job名称与job组名称组成Scheduler中任务的唯一标识.usingJobData("message","我是第一个job的参数")//设置参数信息.build();//构建return jobDetail;}
运行结果:
----------创建调度任务成功----------
----------Hello World--------------
JobDetail的名称和组名:first_group.first_job
调度器Scheduler name: QuartzScheduler
job的类全名:class com.zdw.zixue.job.FirstJob
本次任务执行时间:Wed Sep 25 17:02:26 CST 2019
下次任务执行时间:Wed Sep 25 17:02:36 CST 2019
参数信息:我是第一个job的参数
----------Hello World--------------
JobDetail的名称和组名:first_group.first_job
调度器Scheduler name: QuartzScheduler
job的类全名:class com.zdw.zixue.job.FirstJob
本次任务执行时间:Wed Sep 25 17:02:36 CST 2019
下次任务执行时间:Wed Sep 25 17:02:46 CST 2019
参数信息:我是第一个job的参数
----------Hello World--------------
JobDetail的名称和组名:first_group.first_job
调度器Scheduler name: QuartzScheduler
job的类全名:class com.zdw.zixue.job.FirstJob
本次任务执行时间:Wed Sep 25 17:02:46 CST 2019
下次任务执行时间:Wed Sep 25 17:02:56 CST 2019
参数信息:我是第一个job的参数
---------------关闭了调度器----------------
可以看到,在FirstJob中,可以获取到我们设置的参数message的值。
如果你使用的是持久化的存储机制(本教程的JobStore部分会讲到),在决定JobDataMap中存放什么数据的时候需要小心,因为JobDataMap中存储的对象都会被序列化,因此很可能会导致类的版本不一致的问题;Java的标准类型都很安全,如果你已经有了一个类的序列化后的实例,某个时候,别人修改了该类的定义,此时你需要确保对类的修改没有破坏兼容性;
3.2 有状态Job和无状态Job
有状态的Job可以理解为多次Job调用期间可以持有一些状态信息,这些状态信息存储在JobDataMap中,而默认的无状态job每次调用时都会创建一个新的JobDataMap。
3.2.1 有状态的Job示例
靠注解@PersistJobDataAfterExecution实现
修改类QuartzServer中的createJobDetail方法,设置参数的值,如下:
//创建JobDetailpublic static JobDetail createJobDetail(){JobDetail jobDetail = JobBuilder.newJob(FirstJob.class)//设置执行任务的类.withIdentity("first_job", "first_group")//job名称与job组名称组成Scheduler中任务的唯一标识//.usingJobData("message","我是第一个job的参数")//设置参数信息.usingJobData("count",0)//设置参数.build();//构建return jobDetail;}
FirstJob修改为如下:
package com.zdw.zixue.job;import org.quartz.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;@PersistJobDataAfterExecution//添加了这个注解
public class FirstJob implements Job {private static Logger logger = LoggerFactory.getLogger(FirstJob.class);//日志对象@Overridepublic void execute(JobExecutionContext context) throws JobExecutionException {JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();int count = jobDataMap.getInt("count");System.out.println("count: "+count);++count;jobDataMap.put("count", count);}
}
运行结果:
----------创建调度任务成功----------
count: 0
count: 1
count: 2
---------------关闭了调度器----------------
3.2.2 无状态的Job示例
不加注解@PersistJobDataAfterExecution
package com.zdw.zixue.job;import org.quartz.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;public class FirstJob implements Job {private static Logger logger = LoggerFactory.getLogger(FirstJob.class);//日志对象@Overridepublic void execute(JobExecutionContext context) throws JobExecutionException {JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();int count = jobDataMap.getInt("count");System.out.println("count: "+count);++count;jobDataMap.put("count", count);}
}
运行结果:
----------创建调度任务成功----------
count: 0
count: 0
count: 0
---------------关闭了调度器----------------
可见 @PersistJobDataAfterExecution的作用在于持久化保存在JobDataMap中的传递参数,使得多次执行Job,可以获取传递参数的状态信息。
Job 有一个 StatefulJob 子接口(Quartz 2 后用 @PersistJobDataAfterExecution
注解代替),代表有状态的任务,该接口是一个没有方法的标签接口,其目的是让 Quartz 知道任务的类型,以便采用不同的执行方案。
无状态任务在执行时拥有自己的 JobDataMap 拷贝,对 JobDataMap 的更改不会影响下次的执行。
有状态任务共享同一个 JobDataMap 实例,每次任务执行对 JobDataMap 所做的更改会保存下来,后面的执行可以看到这个更改,也即每次执行任务后都会对后面的执行发生影响。
正因为这个原因,无状态的 Job 能并发执行,而有状态的 StatefulJob 不能并发执行。这意味着如果前次的 StatefulJob 还没有执行完毕,下一次的任务将阻塞等待,直到前次任务执行完毕。有状态任务比无状态任务需要考虑更多的因素,程序往往拥有更高的复杂度,因此除非必要,应该尽量使用无状态的 Job。
3.3 @DisallowConcurrentExecution
quartz中另一个常用的注解为@DisallowConcurrentExecution,该注解可以同一个时刻,同一个任务只能执行一次,不能并行执行两个或多个同一任务。但需要注意的是,多个不同的任务是可以同时执行的。
Quartz学习总结之核心接口Scheduler、Job相关推荐
- Hibernate学习(二):heibernate核心接口
Hibernate是一种对JDBC做了轻量级封装的对象---关系映射工具,所谓轻量级封装,是指Hibernate并没有完全封装JDBC,Java应用即可以通过Hibernate API访问数据库,还可 ...
- Hibernate学习笔记_核心幵发接口及三种对象状态
核心接口开发 (重点) 一, Configuration a) AnnotationConfi ...
- Delphi面向对象学习随笔六:接口
Delphi面向对象学习随笔六:接口 Delphi面向对象学习随笔六:接口 作者:巴哈姆特 (转载请注明出处并保持完整) 在对象化中,类的继承是一个非常强大的机制:而更加强大的继承机制应该是来自从 ...
- 浅谈三个星期零基础入门学习Thinkphp5开发restful-api接口的心得和总结
一丢丢心得体会: 首先不得不说一下,学习一门知识,真的就像建一栋高楼一样,地基必须的稳固,否则你辛辛苦苦建的楼可能随时会垮掉,这一点在我学习thinkphp5的路上深有体会,同时了自此我也爱上了写博客 ...
- Quartz学习资料地址记录 、Quartz 学习的博客地址记录
Quartz专栏系列 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家.点击跳转到教程. 1.Quartz学习--Quartz大致介绍(一) 2.Quartz学习--Q ...
- Mybatis源码阅读(四):核心接口4.2——Executor(上)
*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如 ...
- Mybatis源码阅读(四):核心接口4.1——StatementHandler
*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如 ...
- (转)Java任务调度框架Quartz入门教程指南(三)任务调度框架Quartz实例详解深入理解Scheduler,Job,Trigger,JobDetail...
http://blog.csdn.net/zixiao217/article/details/53053598 首先给一个简明扼要的理解: Scheduler 调度程序-任务执行计划表,只有安排进执行 ...
- Quartz学习总结(2)——定时任务框架Quartz详解
一.概述 Quartz是OpenSymphony开源组织的一个开源项目,定时任务框架,纯Java语言实现,最新版本为2.3.0. Quartz中用到的设计模式:Builder模式.Factory模式. ...
最新文章
- 论文浅尝 | 利用Lattice LSTM的最优中文命名实体识别方法
- PyMC3和Lasagne构建神经网络(ANN)和卷积神经网络(CNN)
- mysql使用sql语句查询数据库所有表注释已经表字段注释
- Sql Server 2005各大版本区别与下载
- 1一10到时的英文单词_[1-10的英语单词读音]1到10的英语单词
- 上班打卡--- 通过批处理命令执行jar文件来记录上班时间
- 使用npm安装yarn
- Kossel 升级记 - 混乱之始
- 微信公众号文章上传附件怎么上传?
- Linux Deepin15.9下更新nvidia显卡驱动
- Altium Designer 总结
- C printf() 详解之终极无惑
- NBUT 1218 You are my brother
- 【ant-design】分页器英文如何转中文
- windows环境下利用python进行CGI配置
- LQ0018 顺子日期【枚举+日期】
- 谷歌和百度常见搜索技巧
- 1.1关于机器学习和深度学习
- 前端入门: 用css设置文字样式。
- 2022年第十三届蓝桥杯 python B组 第B题 寻找整数