Quartz
一、Quartz簡介
Quartz官網(wǎng):http://www.quartz-scheduler.org/
是一個定時任務調(diào)度框架。可以用來處理這樣的問題:
- 想在30分鐘后,查看訂單是否支付,未支付則取消訂單
- 想在每個月29號信用卡自動還款
- ......
- 想定時在某個時間,去做某件事(執(zhí)行某個任務)
Quartz是要做定時任務的調(diào)度,設置好觸發(fā)時間規(guī)則,以及相應的任務(Job)即可。
二、Quartz的使用
2.1 核心類的說明
Scheduler:調(diào)度器。所有的調(diào)度都是由它來控制,是Quartz的大腦,所有任務都是由它來管理。
Job:任務。想定時執(zhí)行的事情(定義業(yè)務邏輯)。
JobDetail:基于Job,進一步包裝。其中關聯(lián)一個Job,并為Job指定更詳細的屬性,比如標識等。
Trigger:觸發(fā)器。可以指定給某個任務,指定任務的觸發(fā)機制。
2.2 Quartz的實現(xiàn)
Step1:導入依賴
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.3</version>
</dependency>
Step2:定義Job
/**
* 工作類的具體實現(xiàn), 即需要定時執(zhí)行的"某件事"
*/
public class HelloQuartzJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println("Job 定時執(zhí)行中 ..." + new Date()); // 打印時間, 方便觀察時間間隔
// 可以從context中獲取JobDetail, 從而得到JobDetail的name和group
JobDetail jobDetail = context.getJobDetail();
JobKey key = jobDetail.getKey();
System.out.println(key.getName());
System.out.println(key.getGroup());
}
}
Step3:創(chuàng)建調(diào)度器和觸發(fā)器,將Job包裝為JobDetail來實現(xiàn)任務的定時調(diào)度
public class HelloQuartz {
@Test
public void testQuartz() throws Exception{
// 1. 創(chuàng)建調(diào)度器
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// 2. 創(chuàng)建觸發(fā)器, 指定定時規(guī)則
// 定義觸發(fā)器的name和所屬的group
SimpleTrigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1")
// 計時起始時間(startNow表示程序一啟動就開始計時)
.startNow()
// 執(zhí)行Job的規(guī)則. 當前定義為2秒執(zhí)行1次, repeatForever表示一直執(zhí)行到計時結束, 也可以用withRepeatCount(10), 表示到計時結束之前最多執(zhí)行10次
.withSchedule(
SimpleScheduleBuilder
.simpleSchedule()
.withIntervalInSeconds(2)
.repeatForever())
// 定義計時結束時間(年, 月, 日, 時, 分, 秒)
.endAt(new GregorianCalendar(2020, 11, 9, 22, 07, 30).getTime())
.build();
// 3. 包裝Job為JobDetail, 并定義JobDetail的name和所屬的group(注意: JobDetail的group不是觸發(fā)器的group, 兩者是相互隔離的, 即使group的名字相同)
JobDetail jobDetail = JobBuilder.newJob(HelloQuartzJob.class).withIdentity("job1", "group1").build();
// 4. 將JobDetail和觸發(fā)器注冊到調(diào)度器中
scheduler.scheduleJob(jobDetail, trigger);
// 5. 啟動, 調(diào)度器開始工作
scheduler.start();
}
}
2.3 Quartz的配置文件
名為quartz.properties,一般放置在 main/resources 下,如果沒有此配置文件則按默認配置啟動
# 指定調(diào)度器名稱, 非實現(xiàn)類
org.quartz.scheduler.instanceName = DefaultQuartzScheduler
# 指定Quartz所用的線程池實現(xiàn)類
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
# 指定線程池線程的數(shù)量
org.quartz.threadPool.threadCount = 10
# 線程優(yōu)先級
org.quartz.threadPool.threadPriority = 5
# 非持久化job(即任務都放到了內(nèi)存中)
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
三、觸發(fā)器Trigger
3.1 SimpleTrigger
以一定的時間間隔執(zhí)行任務
- 指定計時起始時間、截止時間
- 指定時間間隔、執(zhí)行次數(shù)
示例:
// 定義觸發(fā)器的name和所屬的group
SimpleTrigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1")
// 計時起始時間(startNow表示程序一啟動就開始計時)
.startNow()
// 執(zhí)行Job的規(guī)則. 當前定義為2秒執(zhí)行1次, repeatForever表示一直執(zhí)行到計時結束, 也可以用withRepeatCount(10), 表示到計時結束之前最多執(zhí)行10次
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever())
// 定義計時結束時間(年, 月, 日, 時, 分, 秒)
.endAt(new GregorianCalendar(2020, 11, 9, 22, 07, 30).getTime())
.build();
3.2 CronTrigger(重點)
適合于更復雜的任務,它支持類似于Linux Cron的語法(并且更強大)
- 指定Cron表達式即可
示例:
// 不需要指定起始時間和結束時間
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1")
// 表示定時在5月3日18時20分10秒時執(zhí)行任務
.withSchedule(CronScheduleBuilder.cronSchedule("10 20 18 3 5 ?")).build();
3.2.1 Cron表達式組成
表達式組成:"秒 分 時 日 月 星期幾 [年]",其中"年"是可選的,一般不指定
- 如:"10 20 18 3 5 ?" 表示 "5月3日18時20分的第10秒,星期幾不確定"
| 位置 | 時間域 | 允許值 | 特殊值 |
|---|---|---|---|
| 1 | 秒 | 0-59 | , - * / |
| 2 | 分 | 0-59 | , - * / |
| 3 | 時 | 0-23 | , - * / |
| 4 | 日 | 1-31 | , - * ? / L W |
| 5 | 月 | 1-12 | , - * / |
| 6 | 星期幾 | 1-7(1代表周日,7代表周六) | , - * ? / L # |
| 7 | 年(可選) | , - * / |
3.2.2 Cron表達式的特殊值
- ",":用來隔開幾個不連續(xù)的值。例如:"10,15,30 20 18 3 5 ?"表示 "5月3日18時20分的第10秒、15秒、30秒執(zhí)行一次任務,星期幾不確定"
- "-":用來表示一段連續(xù)的值。例如:"10-30 20 18 3 5 ?"表示 "5月3日18時20分的第10秒至第30秒執(zhí)行一次任務,星期幾不確定"
- "" :表示任意值,即"每一個都"。例如:"10 * 18 3 5 ?"表示 "5月3日18時每一分鐘*的第10秒執(zhí)行一次任務,星期幾不確定"
- "/" :表示步長。例如:"10/3 20 18 3 5 ?"表示 "5月3日18時20分的第10秒開始,每3秒執(zhí)行一次任務,星期幾不確定"
- "?":表示不確定。因為日期和星期可能是會有沖突的,所以一般指定其中的一個另一個就用"?"可以避免發(fā)生沖突
- "L":該字符只用在日期和星期中,表示"Last"。如果用在日期,則表示這個月份的最后一天;如果用在星期則表示星期六(等同于7);如果和數(shù)字一起用在星期,例如 6L,則表示該月的最后一個星期五
- "W":該字符只用在日期中,表示"Work"工作日。例如 15W,表示距離該月(注意:不能跨月)15日最近的工作日;和L組合,LW表示該月的最后一個工作日
Cron表達式解析工具:http://bejson.com/othertools/cronvalidate/
四、Spring整合Quartz
4.1 Spring整合Quartz
Step1:導入依賴
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.3</version>
</dependency>
Step2:定義Job
/**
* 工作類的具體實現(xiàn), 即需要定時執(zhí)行的"某件事"
*/
public class MyJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println("Job 定時執(zhí)行中 ..." + new Date()); // 打印時間, 方便觀察時間間隔
// 可以從context中獲取JobDetail, 從而得到JobDetail的name和group
JobDetail jobDetail = context.getJobDetail();
JobKey key = jobDetail.getKey();
System.out.println(key.getName());
System.out.println(key.getGroup());
}
}
Step3:編寫Spring的配置文件applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 1. 定義JobDetail的bean, 作用是生產(chǎn)一個JobDetail, 這里使用JobDetailFactoryBean, 也可以使用MethodInvokingJobDetailFactoryBean, 配置類似 -->
<bean id="myJobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<!-- 指定JobDetail的name -->
<property name="name" value="job1"/>
<!-- 指定JobDetail的group -->
<property name="group" value="job_group1"/>
<!-- 指定具體的Job類(全類名) -->
<property name="jobClass" value="com.lkf.quartz.MyJob"/>
</bean>
<!-- 2. 定義觸發(fā)器的bean, 作用是生產(chǎn)一個觸發(fā)器, 這里使用CronTrigger, 一個觸發(fā)器只能和一個任務進行綁定 -->
<bean id="myTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<!-- 指定觸發(fā)器的name -->
<property name="name" value="trigger1"/>
<!-- 指定觸發(fā)器的group -->
<property name="group" value="trigger_group1"/>
<!-- 指定觸發(fā)器綁定的JobDetail -->
<property name="jobDetail" ref="myJobDetail"/>
<!-- 指定Cron表達式-->
<property name="cronExpression" value="* * * * * ?"/>
</bean>
<!-- 3. 定義調(diào)度器 -->
<bean id="myScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<!-- 將觸發(fā)器注冊到調(diào)度器中 -->
<property name="name" value="triggers">
<list>
<ref bean="myTrigger">
</list>
</property>
<!-- 指定quartz配置文件 -->
<property name="quartzProperties" value="classpath:quartz.properties"/>
</bean>
</beans>
Step4:測試。啟動工廠即可
public class QuartzTest {
@Test
public void test() {
// 工廠啟動, 任務啟動; 工廠關閉, 任務停止
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
}
}
4.2 任務操作
4.2.1 刪除任務
public class QuartzTest {
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 1. 獲取調(diào)度器
StdScheduler scheduler = (StdScheduler)context.getBean("myScheduler");
// 2. 過5秒鐘刪除任務
Thread.sleep(5000);
// 指定要刪除的JobDetail的name和group
scheduler.deleteJob(JobKey.jobKey("job1", "job_group1"));
}
}
4.2.2 暫停、恢復任務
public class QuartzTest {
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 1. 獲取調(diào)度器
StdScheduler scheduler = (StdScheduler)context.getBean("myScheduler");
// 2. 任務暫停過5秒鐘后恢復
scheduler.pauseJob(JobKey.jobKey("job1", "job_group1"));
Thread.sleep(5000);
scheduler.resumeJob(JobKey.jobKey("job1", "job_group1"));
}
}
4.2.3 批量操作
只有暫停和恢復有批量操作
public class QuartzTest {
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 1. 獲取調(diào)度器
StdScheduler scheduler = (StdScheduler)context.getBean("myScheduler");
// 2. 將一個group中的所有任務暫停過5秒鐘后恢復
// 僅指定group的名稱即可
scheduler.pauseJobs(GroupMacher.groupEquals("job_group1"));
Thread.sleep(5000);
scheduler.resumeJobs(GroupMacher.groupEquals("job_group1"));
}
}