一、简介
Quartz:http://www.quartz-scheduler.org/
是一个定时任务调度框架。比如你遇到这样的问题:
- 想在30分钟后,查看订单是否支付,未支付则取消订单
- 想在每月29号,信用卡自动还款
- …
- 想定时在某个时间,去做某件事(任务)。
Quartz是要做定时任务的调度,设置好触发时间规则,以及相应的任务(Job)即可。
如果应用程序需要在给定时间执行任务,或者如果系统有连续维护作业,那么Quartz是理想的解决方案。 使用Quartz作业调度应用的示例: 驱动处理工作流程:作为一个新的订单被初始化放置,调度作业到在正好两个小时内,它将检查订单的状态,如果订单确认消息尚未收到命令触发警告通知,以及改变订单的状态为“等待的干预”。 系统维护:调度工作给数据库的内容,每个工
作日(节假日除外平日)在11:30 PM转储到一个XML文件中。 在应用程序内提供提醒服务。
Quartz 可以运行嵌入在另一个独立式应用程序 Quartz 可以在应用程序服务器(或servlet容器)内被实例化,并且参与XA事务 Quartz 可以作为一个独立的程序运行(其自己的Java虚拟机内),可以通过RMI使用 Quartz 可以被实例化,作为独立的项目集群(负载平衡和故障转移功能),用于作业的执行Quartz可以被持久化
二、Quartz使用
1、导入依赖
<dependencies>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
</dependencies>
2、定义job
public class FirstJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println("任务正在执行中......");
//获取到执行的任务对象
JobDetail jobDetail = jobExecutionContext.getJobDetail();
//获取jobDetail的名称
String name = jobDetail.getKey().getName();
String group = jobDetail.getKey().getGroup();
JobDataMap jobDataMap = jobDetail.getJobDataMap();
String string = jobDataMap.getString("name");
System.out.println("正在执行的任务名:"+name+",正在执行任务所在组"+group+",任务数据name的值为:"+string);
}
}
3、测试任务调度运行job
public class QuartzTest {
public static void main(String[] args) {
try {
//1、创建任务调度器,设置执行的时间规则
Trigger trigger = TriggerBuilder.newTrigger()//此处会先检查加载quartz.properties配置文件,如果没有执行默认规则
//设计trigger标识
.withIdentity("trigger1","group1")
.startNow() //启动立即执行
//设置Schedule模型
.withSchedule(SimpleScheduleBuilder
.simpleSchedule()
.withIntervalInSeconds(1) //1秒钟执行一次
.repeatForever()) //一直执行
//设置规则
.build();
//2、创建执行的任务
JobDetail jobDetail = JobBuilder.newJob(FirstJob.class)
//设置job的标识
.withIdentity("job1","group1")
.usingJobData("name","张三")//设置任务数据
.build();
//3、创建计划
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
//设置计划,将jobDetail和trigger加入到计划中
scheduler.scheduleJob(jobDetail,trigger);
//启动计划
scheduler.start();
} catch (SchedulerException e) {
e.printStackTrace();
}
}
}
4、配置文件
# 名为:quartz.properties,放置在classpath下,如果没有此配置则按默认配置启动
# 指定调度器名称,非实现类
org.quartz.scheduler.instanceName = DefaultQuartzScheduler
# 指定线程池实现类
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
# 线程池线程数量
org.quartz.threadPool.threadCount = 10
# 优先级,默认5
org.quartz.threadPool.threadPriority = 5
# 非持久化job,在内存中运行
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
5、核心类说明
Scheduler
:调度器。所有的调度都是由它控制Scheduler就是Quartz的大脑,所有任务都是由它来设施 Scheduler包含一个两个重要组件:JobStore和ThreadPool JobStore是会来存储运行时信息的,包括Trigger,Schduler,JobDetail,业务锁等 ThreadPool就是线程池,Quartz有自己的线程池实现。所有任务的都会由线程池执行
SchdulerFactory
:顾名思义就是来用创建Schduler了,有两个实现:DirectSchedulerFactory和 StdSchdulerFactory。前者可以用来在代码里定制你自己的Schduler参数。后者是直接读取classpath下的quartz.properties(不存在就都使用默认值)配置来实例化Schduler。通常来讲,我们使用StdSchdulerFactory也就足够了。 SchdulerFactory本身是支持创建RMI stub的,可以用来管理远程的Scheduler,功能与本地一样
三、Trigger
1、SimpleTrigger
以一定的时间间隔(单位是毫秒)执行的任务。
- 指定起始和截止时间(时间段)
- 指定时间间隔、执行次数
Trigger trigger = TriggerBuilder.newTrigger()//此处会先检查加载quartz.properties配置文件,如果没有执行默认规则
//设计trigger标识
.withIdentity("trigger1","group1")
.startNow() //启动立即执行
//设置Schedule模型
.withSchedule(SimpleScheduleBuilder
.simpleSchedule()
.withIntervalInSeconds(1) //1秒钟执行一次
.repeatForever()) //一直执行
.endAt(new GregorianCalendar(2020,7,17,11,49).getTime())
.build();
//1、创建任务调度器,设置执行的时间规则
Trigger trigger = TriggerBuilder.newTrigger()//此处会先检查加载quartz.properties配置文件,如果没有执行默认规则
//设计trigger标识
.withIdentity("trigger1","group1")
.startNow() //启动立即执行
//设置Schedule模型
.withSchedule(SimpleScheduleBuilder
.simpleSchedule()
.withIntervalInSeconds(1) //1秒钟执行一次
.withRepeatCount(3)) //执行3次
.endAt(new GregorianCalendar(2020,7,17,11,49).getTime())
.build();
2、CronTrigger
适合于更复杂的任务,它支持类型于Linux Cron的语法(并且更强大)。
- 指定Cron表达式即可
示例:
从第3s开始,每3秒运行一次
//1、创建任务调度器,设置执行的时间规则
Trigger trigger = TriggerBuilder.newTrigger()//此处会先检查加载quartz.properties配置文件,如果没有执行默认规则
//设计trigger标识
.withIdentity("trigger1","group1")
.startNow() //启动立即执行
//设置Schedule模型
.withSchedule(CronScheduleBuilder.cronSchedule("3/3 * * * * ?"))
.build();
Cron表达式组成
表达式组成:“秒 分 时 日 月 星期几 [年]” ,其中"年" 是可选的,一般不指定。
如:"10 20 18 3 5 ?"代表"5月3日18点20分10秒,星期几不确定 "
位置 | 时间域 | 允许值 | 特殊值 |
---|---|---|---|
秒 | 0-59 | , - * / | |
分钟 | 0-59 | , - * / | |
小时 | 0-23 | , - * / | |
日期 | 1-31 | , - * ? / L W | |
月份 | 1-12 | , - * / | |
星期 | 1-7 | , - * ? / L # | |
年份(可选) | , - * / |
Cron表达式符号
表达式中可使用的特殊符号的含义如下
符号 | 语义 |
---|---|
星号(*) | 可用在所有字段中,表示对应时间域的每一个时刻,例如, 在分钟字段时,表示“每分钟” |
问号(?) | 该字符只在日期和星期字段中使用,它通常指定为“不确定值” |
减号(-) | 表达一个范围,如在小时字段中使用“10-12”,则表示从10到12点,即10,11,12 |
逗号(,) | 表达一个列表值,如在星期字段中使用“MON,WED,FRI”,则表示星期一,星期三和星期五 |
斜杠(/) | x/y表达一个等步长序列,x为起始值,y为增量步长值。如在分钟字段中使用0/15,则表示为0,15,30和45秒,而5/15在分钟字段 中表示5,20,35,50 |
井号(#) | 该字符只用在星期字段中,"4#2"代表第二个星期3,“5#4”代表第4个星期四 |
L | 该字符只在日期和星期字段中使用,代表“Last”的意思,但它在两个字段中意思不同。 |
如果L用在星期字段里,则表示星期六,等同于7 | |
L出现在星期字段里,而且在前面有一个数值x,则表示“这个月的最后一个周x”,例如,6L表示该月的最后星期五 | |
L在日期字段中,表示这个月份的最后一天,如一月的31号,非闰年二月的28号 | |
W | 该字符只能出现在日期字段里,是对前导日期的修饰,表示离该日期最近的工作日 |
例如15W表示离该月15号最近的工作日,如果该月15号是星期六,则匹配14号星期五;如果 15日是星期日,则匹配16号星期一;如果15号 是星期二,那结果就是15号星期二;但必须 注意关联的匹配日期不能够跨月 | |
LW组合 | 在日期字段可以组合使用LW,它的意思是当月的最后一个工作日 |
Cron表达式示例
演示实例
表示式 | 说明 |
---|---|
0 0 12 * * ? | 每天12点运行 |
0 15 10 * * ? | 每天10:15运行 |
0 15 10 * * ? 2008 | 在2008年的每天10:15运行 |
0 * 14 * * ? | 每天14点到15点之间每分钟运行一次,开始于14:00,结束于14:59。 |
0 0/5 14 * * ? | 每天14点到15点每5分钟运行一次,开始于14:00,结束于14:55。 |
0 0/5 14,18 * * ? | 每天14点到15点每5分钟运行一次,此外每天18点到19点每5钟也运行一次。 |
0 0-5 14 * * ? | 每天14:00点到14:05,每分钟运行一次。 |
0 0-5/2 14 * * ? | 每天14:00点到14:05,每2分钟运行一次。 |
0 10,44 14 ? 3 4 | 3月每周三的14:10分和14:44分,各运行一次。 |
0 15 10 ? * 2-6 | 每周一,二,三,四,五的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 2007- 2009 | 在2007,2008,2009年每个月的最后一个星期五的10:15分运行。 |
四、Spring整合Quartz
1、依赖导入
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>4.3.22.RELEASE</version>
</dependency>
<!--spring事务-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.3.22.RELEASE</version>
</dependency>
2、定义一个job类
public class FirstJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println("任务正在执行中......");
//获取到执行的任务对象
JobDetail jobDetail = jobExecutionContext.getJobDetail();
//获取jobDetail的名称
String name = jobDetail.getKey().getName();
String group = jobDetail.getKey().getGroup();
JobDataMap jobDataMap = jobDetail.getJobDataMap();
String string = jobDataMap.getString("name");
System.out.println("正在执行的任务名:"+name+",正在执行任务所在组"+group+",任务数据name的值为:"+string);
}
}
3、配置
调度器 SchedulerFactoryBean
触发器 CronTriggerFactoryBean
JobDetail JobDetailFactoryBean
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--
spring整合quartz进行配置遵循如下步骤:
1、定义工作的job
2、定义触发器Trigger,并将触发器与工作任务绑定
3、定义调度器,并将Trigger注册到Scheduler
-->
<!--配置执行的任务-->
<bean id="jobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<!--指定job名称-->
<property name="name" value="job1"/>
<!--指定job分组-->
<property name="group" value="group1"/>
<!--指定job的具体类-->
<property name="jobClass" value="com.booy.quartzdemo.SpringJob"/>
<!-- 指定jobDataAsMap -->
<property name="jobDataAsMap">
<map>
<entry key="name" value="张三"/>
</map>
</property>
</bean>
<!--配置任务调度器Trigger-->
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<!--指定Trigger名称-->
<property name="name" value="trigger"/>
<!--指定Trigger组名 -->
<property name="group" value="group1"/>
<!--指定Trigger的定时任务cron表达式,从3s开始每5s运行一次-->
<property name="cronExpression" value="3/5 * * * * ?"/>
<!--指定Trigger的jobDetail-->
<property name="jobDetail" ref="jobDetail"/>
</bean>
<!--配置计划,将Trigger注册进scheduler-->
<bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<!--配置调度器-->
<property name="triggers" ref="cronTrigger"/>
<!--可配置多个cronTrigger-->
<!--<property name="triggers">
<list>
<ref bean="cronTrigger"/>
</list>
</property>-->
<!--添加 quartz 配置,如下两种方式均可-->
<!--<property name="configLocation" value="classpath:quartz.properties"> </property>-->
<!-- <property name="quartzProperties">
<value>
配置信息
</value>
</property>-->
</bean>
</beans>
4、操作
启动任务
工厂启动,调度器启动,任务调度开始
public class MyMain {
public static void main(String[] args) {
// 工厂启动,任务启动,工厂关闭,任务停止
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
}
}
任务操作
删除任务
public class MyMain {
public static void main(String[] args) {
// 工厂启动,任务启动,工厂关闭,任务停止
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Scheduler scheduler = (Scheduler)applicationContext.getBean("scheduler");
System.out.println(scheduler.getClass());
try {
Thread.sleep(10000);
scheduler.deleteJob(JobKey.jobKey("job1","group1"));//删除任务
} catch (InterruptedException | SchedulerException e) {
e.printStackTrace();
}
}
}
暂停、恢复
public class MyMain {
public static void main(String[] args) {
// 工厂启动,任务启动,工厂关闭,任务停止
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Scheduler scheduler = (Scheduler)applicationContext.getBean("scheduler");
try {
Thread.sleep(10000);
scheduler.pauseJob(JobKey.jobKey("job1","group1"));//暂停工作
Thread.sleep(3000);
scheduler.resumeJob(JobKey.jobKey("job1","group1"));//恢复工作
} catch (InterruptedException | SchedulerException e) {
e.printStackTrace();
}
}
}
批量操作
public class MyMain {
public static void main(String[] args) {
// 工厂启动,任务启动,工厂关闭,任务停止
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Scheduler scheduler = (Scheduler)applicationContext.getBean("scheduler");
try {
Thread.sleep(10000);
GroupMatcher<JobKey> group1 = GroupMatcher.groupEquals("group1");
scheduler.pauseJobs(group1); //暂停组中所有工作
Thread.sleep(10000);
scheduler.resumeJobs(group1); //恢复组中所有工作
} catch (InterruptedException | SchedulerException e) {
e.printStackTrace();
}
}
}
五、 其他任务调度方式
1、使用Timer创建简单的定时任务
public class TimerDemo {
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("TimerTask run:"+ LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
}
},1000,3000);//延时1s,之后每隔3s运行一次
}
}
注意:
1)、scheduleAtFixedRate 和 schedule 的区别: scheduleAtFixedRate 会尽量减少漏掉调度的情况,如果前一次执行时间过长,导致一个或几个任务漏掉了,那么会补回来,而schedule 过去的不会补,直接加上间隔时间执行下一次任务。
2)、同一个 Timer 下添加多个 TimerTask ,如果其中一个没有捕获抛出的异常,则全部任务都会终止运行。但是多个 Timer 是互不影响
2、在springboot环境下使用@Scheduled创建定时任务
步骤:
1)、启动类添加 @EnableScheduling
2)、定时任务方法上添加 @Scheduled
@Component
public class springScheduledDemo {
@Scheduled(cron = "1/5 * * * * ?")
public void testScheduled() {
System.out.println("springScheduled run:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
}
}