Quartz
Job
Job是一个接口,只有一个方法
void execute(JobExecutionContext ctx) throws JobExecutionException;
开发者实现该接口定义需要执行的任务。这个方法的参数JobExecutionContext ctx提供了调度上下文中的信息。
下面的demo片段如下:
public class HelloJob implements Job {
/**
* 编写对应的业务逻辑
*/
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println();
Date date = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("Current Exec Time Is: " + simpleDateFormat.format(date));
System.out.println("hello World");
}
}
public class HelloSchedule {
public static void main(String[] args) throws SchedulerException {
//jobdetail 绑定信息job
JobDetail jobDetail = JobBuilder.newJob(HelloJob.class).withIdentity("myJob", "group1").build();
//创建一个Trigger实例,定义该Job立即执行,并且每隔两秒钟重复执行一次,知道永远
Trigger trigger = TriggerBuilder.newTrigger().
withIdentity("myTrigger", "group1").
startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever()
).build();
//创建Scheduler实例,通过factory的模式创建
StdSchedulerFactory stdSchedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = stdSchedulerFactory.getScheduler();
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
}
}
生命周期
每次调度器执行job时,他在调用execute方法前会创建一个新的job实例,当调用完成后,关联的job对象实例会被释放,释放的实例会被垃圾回收机制回收
JobDetail为Job实例提供了许多设置属性,以及JobDataMap成员变量属性,它用来存储特定Job实例的状态信息,调度器需要借助JobDetail对象来添加Job实例。
JobDetail
Quartz在每次执行job时,都重新创建一个job实例,所以,quartz不是接受一个job实例,而是一个Job实现类,在运行时,通过newInstance()的反射调用机制实例化Job。因此,需要通过一个对象来描述Job的实现类及一些静态信息,如Job名称,组名,关联监听器等信息。
- name:任务属性
- group:组
- jobClass:任务的实现类
- jobDataMap:用来传参的作用
我们需要在job运行的时候,动态的判断,并且写日志,记录哪一个job执行,通过jobDetail来获取
JOBDataMap
- 在进行任务调度时,jobDataMap存储在JobExecutionContext中,非常方便获取
- JobDataMap可以用来装载任何可序列化的数据对象,当job实例对象被执行时,这些参数对象会传递给它。
- jobDataMap实现了JDK的Map接口,并且添加了一些非常方便的方法用来存取基本数据类型。
public static void main(String[] args) throws SchedulerException {
//jobdetail 绑定信息job
JobDetail jobDetail = JobBuilder.newJob(HelloJob.class).withIdentity("myJob", "group1").usingJobData("message", "helloMyJob1").
usingJobData("helloJob", "wow").build();
System.out.println(jobDetail.getKey());
//创建一个Trigger实例,定义该Job立即执行,并且每隔两秒钟重复执行一次,知道永远
Trigger trigger = TriggerBuilder.newTrigger().
withIdentity("myTrigger", "group1").
startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever()
).usingJobData("myTrigger", "triggerString").usingJobData("helloTrigger", "wow").build();
//创建Scheduler实例,通过factory的模式创建
StdSchedulerFactory stdSchedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = stdSchedulerFactory.getScheduler();
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
}
从Map中直接获取
public class HelloJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println();
Date date = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("Current Exec Time Is: " + simpleDateFormat.format(date));
JobKey jobKey = jobExecutionContext.getJobDetail().getKey();
System.out.println(jobKey.getName() + jobKey.getGroup());
JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
for (String s : jobDataMap.keySet()) {
System.out.println(s);
System.out.println(jobDataMap.get(s));
}
TriggerKey triggerKey = jobExecutionContext.getTrigger().getKey();
System.out.println(triggerKey.getName() + triggerKey.getGroup());
}
}
获取所有信息
for (String s : mergedJobDataMap.keySet()) {
System.out.println(s);
System.out.println(mergedJobDataMap.get(s));
}
但是上述方式,如果trigger中的key和job的key冲突,那么trigger中的key会覆盖job中的key
通过Setter获取
Job实现类中添加Setter方法,对应JobDataMap的键值(Quartz框架默认的JobFactory实现类在初始化job实例对象时会自动的调用这些setter方法)
-
public class HelloJob implements Job { private String message; public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } /** * 编写对应的业务逻辑 */ @Override public void execute(JobExecutionContext jobExecutionContext) { System.out.println(getMessage()); } }
Trigger
Trigger描述触发job执行的时间规则。有SimpleTrigger和CronTrigger这两个子类。SimpleTrigger主要用于触发一次或者固定频率触发;CronTrigger则可以通过Cron表达式定义各种复杂的触发时间,如每周一上午10点执行、每月的最后一个星期日执行。这个跟Linux中的cron定时任务是一样的,具体的定义规则,自己google吧。
JobKey
表示job实例的标识,触发器触发时,该指定的job实例会被执行
StartTime
表示触发器首次被触发的时间,比如说五一才开始执行
endTime
表示触发器不再被执行
的时间
Trigger trigger = TriggerBuilder.newTrigger().
withIdentity("myTrigger", "group1").
//当前时间开始
//startNow()
//开始时间
startAt(new Date()).
//结束时间
endAt(calendar.getTime())
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever()
).usingJobData("myTrigger", "triggerString").usingJobData("helloTrigger", "wow").build();
SimpleTrigger
SimpleTrigger可以满足的调度需求是:在具体的时间点执行一次,或者在具体的时间点执行,并且以指定的间隔重复执行若干次。比如,你有一个trigger,你可以设置它在2015年1月13日的上午11:23:54准时触发,或者在这个时间点触发,并且每隔2秒触发一次,一共重复5次。
Calendar
有时每月的最后一个星期日执行任务,但是如果遇到中秋节、春节就不执行,这种需求就用到了Calendar。也就是说,从tigger中排除若干个特殊日期,使用的就是Calendar。一个trigger可以和多Calendar配合使用。
CronTrigger
能够实现更复杂的业务逻辑,比如说每周五执行任务
表达式
CronTrigger配置完整格式为: 秒
分
时
日
月
周
年
序号 | 说明 | 是否必填 | 允许范围 | 允许的通配符 |
---|---|---|---|---|
1 | 秒 | 是 | 0-59 | ,- * / |
2 | 分 | 是 | 0-23 | , - * / |
3 | 时 | 是 | 1-31 | , - * / |
4 | 日 | 是 | 1-12 | , - * ? / L W |
5 | 月 | 是 | 1-12 or JAN-DEC | , - * / |
6 | 周 | 是 | 1-12 or SUN-SAT | , - * ? / L # |
7 | 年 | 否 | empty 或 1970-2099 | , - * / |
通配符说明
*
- 表示所有值. 例如:在分的字段上设置 “*”,表示每一分钟都会触发。
?
- 表示不指定值。使用的场景为不需要关心当前设置这个字段的值。例如:要在每月的10号触发一个操作,但不关心是周几,所以需要周位置的那个字段设置为"?" 具体设置为 0 0 0 10 * ?- 表示区间。例如 在小时上设置 “10-12”,表示 10,11,12点都会触发。
,
- 表示指定多个值,例如在周字段上设置 “MON,WED,FRI” 表示周一,周三和周五触发
/
- 用于递增触发。如在秒上面设置"5/15" 表示从5秒开始,每增15秒触发(5,20,35,50)。 在月字段上设置’1/3’所示每月1号开始,每隔三天触发一次。
L
- 表示最后的意思。在日字段设置上,表示当月的最后一天(依据当前月份,如果是二月还会依据是否是润年[leap]), 在周字段上表示星期六,相当于"7"或"SAT"。如果在"L"前加上数字,则表示该数据的最后一个。例如在周字段上设置"6L"这样的格式,则表示“本月最后一个星期五"
W
- 表示离指定日期的最近那个工作日(周一至周五). 例如在日字段上设置"15W",表示离每月15号最近的那个工作日触发。如果15号正好是周六,则找最近的周五(14号)触发, 如果15号是周未,则找最近的下周一(16号)触发.如果15号正好在工作日(周一至周五),则就在该天触发。如果指定格式为 “1W”,它则表示每月1号往后最近的工作日触发。如果1号正是周六,则将在3号下周一触发。(注,“W"前只能设置具体的数字,不允许区间”-").
#
- 序号(表示每月的第几个周几),例如在周字段上设置"6#3"表示在每月的第三个周六.注意如果指定"#5",正好第五周没有周六,则不会触发该配置(用在母亲节和父亲节再合适不过了) ;
- 小提示:
L
和W
可以一组合使用。如果在日字段上设置LW
,则表示在本月的最后一个工作日触发;- 周字段的设置,若使用英文字母是不区分大小写的,即
MON
与mon
相同;
举例
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/5 14 * * ? 每天下午的 2点到2点59分(整点开始,每隔5分触发)
0 0/5 14,18 * * ? 每天下午的 2点到2点59分、18点到18点59分(整点开始,每隔5分触发)
0 0-5 14 * * ? 每天下午的 2点到2点05分每分触发
0 10,44 14 ? 3 WED 3月分每周三下午的 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 从2002年到2005年每月最后一周的星期五的10点15分触发
0 15 10 ? * 6#3 每月的第三周的星期五开始触发
0 0 12 1/5 * ? 每月的第一个中午开始每隔5天触发一次
0 11 11 11 11 ? 每年的11月11号 11点11分触发(光棍节)
Scheduler调度器
Scheduler代表一个Quartz独立运行的容器,Trigger和JobDetail可以注册到Scheduler中,两者在Scheduler中拥有各自的组和名称, 组和名称是在Scheduler中定位一个对象的依据。
Scheduler Factory
StdSchedulerFactory
-
使用一组参数(java.util.Properties)来创建和初始化Quartz调度器
-
配置参数一般存储在quartz.properties
-
调用getScheduler方法就能创建和初始化调度器对象
API
最近一次执行的时间
Date date = scheduler.scheduleJob(build, cronTrigger);
System.out.println(date);
挂起
scheduler.standby();
挂起之后执行
scheduler.start()
停止
这种方式停止之后scheduler再次启动是无法启动的,boolean 参数如果是true则等待当前持有的任务运行完成之后再停止
scheduler.shutdown(boolean b )
判断是否停止
boolean b = scheduler.isShutDown();
QuartzProperties
文档的位置和顺序
默认加载的是工厂目录下的QuartzProperties如果没有则读取jar包中的
quartz.properties
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-delMscNv-1574768763273)(https://ws4.sinaimg.cn/large/006tNc79gy1g1wfcic0clj31880n6q8w.jpg)]
组成部分
调度器属性
org.quartz.scheduler.instanceName
:属性用来区分特定的调度器实例,可以按照功能用途来给调度器命名。
org.quartz.scheduler.instanceId
:属性和前者一样,也运行任何字符串,但是这个值必须是在所有调度器实例中唯一,尤其在一个集群当中,作为集群的唯一key,如果我们想quartz帮我们生成这个值的话,可以设置auto
xml配置方式添加Quartz
MethodInvokingJobDetailFactoryBean
调用 myBean的printMessage方法
@Component("myBean")
public class MyBean {
public void printMessage() {
System.out.println("myBean executes");
}
}
<bean id="simpleJobDetail"
class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="myBean"/>
<property name="targetMethod" value="printMessage"/>
</bean>
JobDetailFactoryBean
需要给作业传递数据,想要更加灵活的方式使用这种方式
<bean id="firstComplexJobDetail"
class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="jobClass"
value="com.zzjson.scheduler.FirstScheduleJob"/>
<property name="jobDataMap">
<map>
<entry key="anotherBean" value-ref="anotherBean"/>
</map>
</property>
<property name="Durability" value="true"/>
</bean>
<bean id="firstTimer" class="com.gupaoedu.timer.XmlTimer"></bean>
<bean id="firstTask"
class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject">
<ref bean="firstTimer" />
</property>
<property name="targetMethod">
<value>execute</value>
</property>
</bean>
<bean id="firstTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail">
<ref bean="firstTask" />
</property>
<property name="cronExpression">
<value>0/5 * * * * ?</value>
</property>
</bean>
<bean id="startQuertz" lazy-init="false" autowire="no"
class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="firstTrigger" />
</list>
</property>
</bean>
Annotation方式添加
利用反射机制实现定时任务的动态调度
实现 ApplicationContextAware
扫描类
定时任务重复启动的问题
1、定时任务重复启动的问题,避免注解和xml方式重复使用
2、同一时间触发多个任务,有些任务不执行的问题,使用多线程异步解决 @Async
quartz.properties
# Default Properties file for use by StdSchedulerFactory
# to create a Quartz Scheduler Instance, if a different
# properties file is not explicitly specified.
#
# ===========================================================================
# Configure Main Scheduler Properties 调度器属性
# ===========================================================================
org.quartz.scheduler.instanceName: DefaultQuartzScheduler
org.quartz.scheduler.instanceid:AUTO
org.quartz.scheduler.rmi.export: false
org.quartz.scheduler.rmi.proxy: false
org.quartz.scheduler.wrapJobExecutionInUserTransaction: false
# ===========================================================================
# Configure ThreadPool 线程池属性
# ===========================================================================
#线程池的实现类(一般使用SimpleThreadPool即可满足几乎所有用户的需求)
org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
#指定线程数,至少为1(无默认值)(一般设置为1-100直接的整数合适)
org.quartz.threadPool.threadCount: 10
#设置线程的优先级(最大为java.lang.Thread.MAX_PRIORITY 10,最小为Thread.MIN_PRIORITY 1,默认为5)
org.quartz.threadPool.threadPriority=5
#设置SimpleThreadPool的一些属性
#设置是否为守护线程
#org.quartz.threadpool.makethreadsdaemons = false
#org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
#org.quartz.threadpool.threadsinheritgroupofinitializingthread=false
#线程前缀默认值是:[Scheduler Name]_Worker
#org.quartz.threadpool.threadnameprefix=swhJobThead;
# 配置全局监听(TriggerListener,JobListener) 则应用程序可以接收和执行 预定的事件通知
# ===========================================================================
# Configuring a Global TriggerListener 配置全局的Trigger监听器
# MyTriggerListenerClass 类必须有一个无参数的构造函数,和 属性的set方法,目前2.2.x只支持原始数据类型的值(包括字符串)
# ===========================================================================
#org.quartz.triggerListener.NAME.class = com.swh.MyTriggerListenerClass
#org.quartz.triggerListener.NAME.propName = propValue
#org.quartz.triggerListener.NAME.prop2Name = prop2Value
# ===========================================================================
# Configuring a Global JobListener 配置全局的Job监听器
# MyJobListenerClass 类必须有一个无参数的构造函数,和 属性的set方法,目前2.2.x只支持原始数据类型的值(包括字符串)
# ===========================================================================
#org.quartz.jobListener.NAME.class = com.swh.MyJobListenerClass
#org.quartz.jobListener.NAME.propName = propValue
#org.quartz.jobListener.NAME.prop2Name = prop2Value
# ===========================================================================
# Configure JobStore 存储调度信息(工作,触发器和日历等)
# ===========================================================================
# 信息保存时间 默认值60秒
org.quartz.jobStore.misfireThreshold: 60000
#保存job和Trigger的状态信息到内存中的类
org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore
# ===========================================================================
# Configure SchedulerPlugins 插件属性 配置
# ===========================================================================
# 自定义插件
#org.quartz.plugin.NAME.class = com.swh.MyPluginClass
#org.quartz.plugin.NAME.propName = propValue
#org.quartz.plugin.NAME.prop2Name = prop2Value
#配置trigger执行历史日志(可以看到类的文档和参数列表)
org.quartz.plugin.triggHistory.class = org.quartz.plugins.history.LoggingTriggerHistoryPlugin
org.quartz.plugin.triggHistory.triggerFiredMessage = Trigger {1}.{0} fired job {6}.{5} at: {4, date, HH:mm:ss MM/dd/yyyy}
org.quartz.plugin.triggHistory.triggerCompleteMessage = Trigger {1}.{0} completed firing job {6}.{5} at {4, date, HH:mm:ss MM/dd/yyyy} with resulting trigger instruction code: {9}
##配置job调度插件 quartz_jobs(jobs and triggers内容)的XML文档
##加载 Job 和 Trigger 信息的类 (1.8之前用:org.quartz.plugins.xml.JobInitializationPlugin)
#org.quartz.plugin.jobInitializer.class = org.quartz.plugins.xml.XMLSchedulingDataProcessorPlugin
#指定存放调度器(Job 和 Trigger)信息的xml文件,默认是classpath下quartz_jobs.xml
#org.quartz.plugin.jobInitializer.fileNames = my_quartz_job2.xml
#org.quartz.plugin.jobInitializer.overWriteExistingJobs = false
#org.quartz.plugin.jobInitializer.failOnFileNotFound = true
#自动扫描任务单并发现改动的时间间隔,单位为秒
#org.quartz.plugin.jobInitializer.scanInterval = 10
#覆盖任务调度器中同名的jobDetail,避免只修改了CronExpression所造成的不能重新生效情况
#org.quartz.plugin.jobInitializer.wrapInUserTransaction = false
# ===========================================================================
# Sample configuration of ShutdownHookPlugin ShutdownHookPlugin插件的配置样例
# ===========================================================================
#org.quartz.plugin.shutdownhook.class = \org.quartz.plugins.management.ShutdownHookPlugin
#org.quartz.plugin.shutdownhook.cleanShutdown = true
#
# Configure RMI Settings 远程服务调用配置
#
#如果你想quartz-scheduler出口本身通过RMI作为服务器,然后设置“出口”标志true(默认值为false)。
#org.quartz.scheduler.rmi.export = false
#主机上rmi注册表(默认值localhost)
#org.quartz.scheduler.rmi.registryhost = localhost
#注册监听端口号(默认值1099)
#org.quartz.scheduler.rmi.registryport = 1099
#创建rmi注册,false/never:如果你已经有一个在运行或不想进行创建注册
# true/as_needed:第一次尝试使用现有的注册,然后再回来进行创建
# always:先进行创建一个注册,然后再使用回来使用注册
#org.quartz.scheduler.rmi.createregistry = never
#Quartz Scheduler服务端端口,默认是随机分配RMI注册表
#org.quartz.scheduler.rmi.serverport = 1098
#true:链接远程服务调度(客户端),这个也要指定registryhost和registryport,默认为false
# 如果export和proxy同时指定为true,则export的设置将被忽略
#org.quartz.scheduler.rmi.proxy = false
Git 地址:https://gitee.com/zzy0_0/learning/tree/master/quartz-parent