Quartz學(xué)習(xí)
(筆記內(nèi)容來源: https://www.cnblogs.com/daxin/archive/2013/05/27/3101972.html)
1.Quartz CronTrigger 最完整配置說明
CronTrigger配置格式:
格式: [秒] [分] [小時] [日] [月] [周] [年]
| 序號 | 說明 | 是否必填 | 允許填寫的值 | 允許的通配符 |
|---|---|---|---|---|
| 1 | 秒 | 是 | 0-59 | , - * / |
| 2 | 分 | 是 | 0-59 | , - * / |
| 3 | 小時 | 是 | 0-23 | , - * / |
| 4 | 日 | 是 | 1-31 | , - * ? / L W |
| 5 | 月 | 是 | 1-12 or JAN-DEC | , - * / |
| 6 | 周 | 是 | 1-7 or SUN-SAT | , - * ? / L # |
| 7 | 年 | 否 | empty 或 1970-2099 | , - * / |
通配符說明:
* 表示所有值. 例如:在分的字段上設(shè)置 "*",表示每一分鐘都會觸發(fā)。
? 表示不指定值。使用的場景為不需要關(guān)心當(dāng)前設(shè)置這個字段的值。例如:要在每月的10號觸發(fā)一個操作,但不關(guān)心是周幾,所以需要周位置的那個字段設(shè)置為"?" 具體設(shè)置為 0 0 0 10 * ?
- 表示區(qū)間。例如 在小時上設(shè)置 "10-12",表示 10,11,12點都會觸發(fā)。
, 表示指定多個值,例如在周字段上設(shè)置 "MON,WED,FRI" 表示周一,周三和周五觸發(fā)
/ 用于遞增觸發(fā)。如在秒上面設(shè)置"5/15" 表示從5秒開始,每增15秒觸發(fā)(5,20,35,50)。 在月字段上設(shè)置'1/3'所示每月1號開始,每隔三天觸發(fā)一次。
L 表示最后的意思。在日字段設(shè)置上,表示當(dāng)月的最后一天(依據(jù)當(dāng)前月份,如果是二月還會依據(jù)是否是潤年[leap]), 在周字段上表示星期六,相當(dāng)于"7"或"SAT"。如果在"L"前加上數(shù)字,則表示該數(shù)據(jù)的最后一個。例如在周字段上設(shè)置"6L"這樣的格式,則表示“本月最后一個星期五"
W 表示離指定日期的最近那個工作日(周一至周五). 例如在日字段上設(shè)置"15W",表示離每月15號最近的那個工作日觸發(fā)。如果15號正好是周六,則找最近的周五(14號)觸發(fā), 如果15號是周未,則找最近的下周一(16號)觸發(fā).如果15號正好在工作日(周一至周五),則就在該天觸發(fā)。如果指定格式為 "1W",它則表示每月1號往后最近的工作日觸發(fā)。如果1號正是周六,則將在3號下周一觸發(fā)。(注,"W"前只能設(shè)置具體的數(shù)字,不允許區(qū)間"-").
# 序號(表示每月的第幾個周幾),例如在周字段上設(shè)置"6#3"表示在每月的第三個周六.注意如果指定"#5",正好第五周沒有周六,則不會觸發(fā)該配置(用在母親節(jié)和父親節(jié)再合適不過了)
Quartz Scheduler 任務(wù)參數(shù)與任務(wù)狀態(tài)
@DisallowConcurrentExecution (簡單來說:不允許任務(wù)還沒結(jié)束,新開線程執(zhí)行任務(wù))
此標(biāo)記用在實現(xiàn)Job的類上面,意思是不允許并發(fā)執(zhí)行,按照我之前的理解是 不允許調(diào)度框架在同一時刻調(diào)用Job類,后來經(jīng)過測試發(fā)現(xiàn)并不是這樣,而是Job(任務(wù))的執(zhí)行時間[比如需要10秒]大于任務(wù)的時間間隔[Interval(5秒)],那么默認(rèn)情況下,調(diào)度框架為了能讓 任務(wù)按照我們預(yù)定的時間間隔執(zhí)行,會馬上啟用新的線程執(zhí)行任務(wù)。否則的話會等待任務(wù)執(zhí)行完畢以后 再重新執(zhí)行!(這樣會導(dǎo)致任務(wù)的執(zhí)行不是按照我們預(yù)先定義的時間間隔執(zhí)行)
測試代碼,這是官方提供的例子。設(shè)定的時間間隔為3秒,但job執(zhí)行時間是5秒,設(shè)置@DisallowConcurrentExecution以后程序會等任務(wù)執(zhí)行完畢以后再去執(zhí)行,否則會在3秒時再啟用新的線程執(zhí)行
org.quartz.threadPool.threadCount = 5 這里配置框架的線程池中線程的數(shù)量,要多配置幾個,否則@DisallowConcurrentExecution不起作用
org.quartz.scheduler.instanceName = MyScheduler
org.quartz.threadPool.threadCount = 5
org.quartz.jobStore.class =org.quartz.simpl.RAMJobStore
@PersistJobDataAfterExecution
@DisallowConcurrentExecution
public class StatefulDumbJob implements Job {
/*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* Constants.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
public static final String NUM_EXECUTIONS = "NumExecutions";
public static final String EXECUTION_DELAY = "ExecutionDelay";
/*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* Constructors.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
public StatefulDumbJob() {
}
/*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* Interface.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
/**
* <p>
* Called by the <code>{@link org.quartz.Scheduler}</code> when a <code>{@link org.quartz.Trigger}</code>
* fires that is associated with the <code>Job</code>.
* </p>
*
* @throws JobExecutionException
* if there is an exception while executing the job.
*/
public void execute(JobExecutionContext context)
throws JobExecutionException {
System.err.println("---" + context.getJobDetail().getKey()
+ " executing.[" + new Date() + "]");
JobDataMap map = context.getJobDetail().getJobDataMap();
int executeCount = 0;
if (map.containsKey(NUM_EXECUTIONS)) {
executeCount = map.getInt(NUM_EXECUTIONS);
}
executeCount++;
map.put(NUM_EXECUTIONS, executeCount);
long delay = 5000l;
if (map.containsKey(EXECUTION_DELAY)) {
delay = map.getLong(EXECUTION_DELAY);
}
try {
Thread.sleep(delay);
} catch (Exception ignore) {
}
System.err.println(" -" + context.getJobDetail().getKey()
+ " complete (" + executeCount + ").");
}
}
public class MisfireExample {
public void run() throws Exception {
Logger log = LoggerFactory.getLogger(MisfireExample.class);
log.info("------- Initializing -------------------");
// First we must get a reference to a scheduler
SchedulerFactory sf = new StdSchedulerFactory();
Scheduler sched = sf.getScheduler();
log.info("------- Initialization Complete -----------");
log.info("------- Scheduling Jobs -----------");
// jobs can be scheduled before start() has been called
// get a "nice round" time a few seconds in the future...
Date startTime = nextGivenSecondDate(null, 15);
// statefulJob1 will run every three seconds
// (but it will delay for ten seconds)
JobDetail job = newJob(StatefulDumbJob.class)
.withIdentity("statefulJob1", "group1")
.usingJobData(StatefulDumbJob.EXECUTION_DELAY, 10000L)
.build();
SimpleTrigger trigger = newTrigger()
.withIdentity("trigger1", "group1")
.startAt(startTime)
.withSchedule(simpleSchedule()
.withIntervalInSeconds(3)
.repeatForever())
.build();
Date ft = sched.scheduleJob(job, trigger);
log.info(job.getKey() +
" will run at: " + ft +
" and repeat: " + trigger.getRepeatCount() +
" times, every " + trigger.getRepeatInterval() / 1000 + " seconds");
log.info("------- Starting Scheduler ----------------");
// jobs don't start firing until start() has been called...
sched.start();
log.info("------- Started Scheduler -----------------");
try {
// sleep for ten minutes for triggers to file....
Thread.sleep(600L * 1000L);
} catch (Exception e) {
}
log.info("------- Shutting Down ---------------------");
sched.shutdown(true);
log.info("------- Shutdown Complete -----------------");
SchedulerMetaData metaData = sched.getMetaData();
log.info("Executed " + metaData.getNumberOfJobsExecuted() + " jobs.");
}
public static void main(String[] args) throws Exception {
MisfireExample example = new MisfireExample();
example.run();
}
}
@PersistJobDataAfterExecution
此標(biāo)記說明在執(zhí)行完Job的execution方法后保存JobDataMap當(dāng)中固定數(shù)據(jù),在默認(rèn)情況下 也就是沒有設(shè)置 @PersistJobDataAfterExecution的時候 每個job都擁有獨立JobDataMap
否則改任務(wù)在重復(fù)執(zhí)行的時候具有相同的JobDataMap
@PersistJobDataAfterExecution
@DisallowConcurrentExecution
public class BadJob1 implements Job {
public BadJob1() {
}
public void execute(JobExecutionContext context)
throws JobExecutionException {
JobKey jobKey = context.getJobDetail().getKey();
JobDataMap dataMap = context.getJobDetail().getJobDataMap();
int denominator = dataMap.getInt("denominator");
System.out.println("---" + jobKey + " executing at " + new Date() + " with denominator " + denominator);
denominator++;
dataMap.put("denominator", denominator);
}
}
public class JobExceptionExample {
public void run() throws Exception {
// First we must get a reference to a scheduler
SchedulerFactory sf = new StdSchedulerFactory();
Scheduler sched = sf.getScheduler();
// jobs can be scheduled before start() has been called
// get a "nice round" time a few seconds in the future...
Date startTime = nextGivenSecondDate(null, 2);
JobDetail job = newJob(BadJob1.class)
.withIdentity("badJob1", "group1")
.usingJobData("denominator", "0")
.build();
SimpleTrigger trigger = newTrigger()
.withIdentity("trigger1", "group1")
.startAt(startTime)
.withSchedule(simpleSchedule()
.withIntervalInSeconds(2)
.repeatForever())
.build();
Date ft = sched.scheduleJob(job, trigger);
//任務(wù)每2秒執(zhí)行一次 那么在BadJob1的方法中拿到的JobDataMap的數(shù)據(jù)是共享的.
//這里要注意一個情況: 就是JobDataMap的數(shù)據(jù)共享只針對一個BadJob1任務(wù)。
//如果在下面在新增加一個任務(wù) 那么他們之間是不共享的 比如下面
JobDetail job2 = newJob(BadJob1.class)
.withIdentity("badJob1", "group1")
.usingJobData("denominator", "0")
.build();
SimpleTrigger trigger2 = newTrigger()
.withIdentity("trigger1", "group1")
.startAt(startTime)
.withSchedule(simpleSchedule()
.withIntervalInSeconds(2)
.repeatForever())
.build();
//這個job2與job執(zhí)行的JobDataMap不共享
sched.scheduleJob(job2, trigger2);
sched.start();
try {
// sleep for 30 seconds
Thread.sleep(30L * 1000L);
} catch (Exception e) {
}
sched.shutdown(false);
}
public static void main(String[] args) throws Exception {
JobExceptionExample example = new JobExceptionExample();
example.run();
}
}
requestRecovery的意思是當(dāng)任務(wù)在執(zhí)行過程中出現(xiàn)意外 比如服務(wù)器down了 那么在重啟時候是否恢復(fù)任務(wù)
JobDetail job = newJob(HelloJob.class)
.withIdentity("job1", "group1")
.storeDurably()
.requestRecovery()
.build();
Quartz Scheduler當(dāng)任務(wù)中出現(xiàn)異常時的處理策略(JobExecutionExceptions)
問題1 如果任務(wù)執(zhí)行發(fā)生錯誤了怎么辦!
Quartz提供了二種解決方法:
- 1 立即重新執(zhí)行任務(wù)
- 2 立即停止所有相關(guān)這個任務(wù)的觸發(fā)器
問題2 怎么去執(zhí)行呢?
Quartz的解決方式是:在你的程序出錯時,用Quartz提供的JobExecutionException類相關(guān)方法很好的解決
一、立即重新執(zhí)行該任務(wù)
當(dāng)任務(wù)中出現(xiàn)異常時,我們捕獲它,然后轉(zhuǎn)換為JobExecutionExceptions異常拋出,同時可以控制調(diào)度引擎立即重新執(zhí)行這個任務(wù)。
try {
int zero = 0;
int calculation = 4815 / zero;
}
catch (Exception e) {
_log.info("--- Error in job!");
JobExecutionException e2 =
new JobExecutionException(e);
// this job will refire immediately
e2.refireImmediately();
throw e2;
}
二、取消所有與這個任務(wù)關(guān)聯(lián)的觸發(fā)器
try {
int zero = 0;
int calculation = 4815 / zero;
}
catch (Exception e) {
_log.info("--- Error in job!");
JobExecutionException e2 =
new JobExecutionException(e);
// Quartz will automatically unschedule
// all triggers associated with this job
// so that it does not run again
e2.setUnscheduleAllTriggers(true);
throw e2;
}
Quartz Scheduler與Spring集成(一) 基礎(chǔ)配置與常見問題
https://www.cnblogs.com/daxin/archive/2013/05/29/3107178.html
常用操作代碼:
http://www.quartz-scheduler.org/documentation/quartz-2.3.0/cookbook/MultipleSchedulers.html
Quartz How-To:Defining a Job (with input data)
Job:
public class PrintPropsJob implements Job {
public PrintPropsJob() {
// Instances of Job must have a public no-argument constructor.
}
public void execute(JobExecutionContext context)
throws JobExecutionException {
JobDataMap data = context.getMergedJobDataMap();
System.out.println("someProp = " + data.getString("someProp"));
}
}
Define job instance:
JobDetail job1 = newJob(MyJobClass.class)
.withIdentity("job1", "group1") //標(biāo)識任務(wù)
.usingJobData("someProp", "someValue") //input data
.build();
Quartz How-To: Scheduling a Job
// Define job instance
JobDetail job1 = newJob(ColorJob.class)
.withIdentity("job1", "group1")
.build();
// Define a Trigger that will fire "now", and not repeat
Trigger trigger = newTrigger()
.withIdentity("trigger1", "group1")
.startNow()
.build();
// Schedule the job with the trigger
sched.scheduleJob(job, trigger);
How-To: Update an existing job
// Add the new job to the scheduler, instructing it to "replace"
// the existing job with the given name and group (if any)
JobDetail job1 = newJob(MyJobClass.class)
.withIdentity("job1", "group1")
.build();
// store, and set overwrite flag to 'true'
scheduler.addJob(job1, true);
How-To: Updating a trigger
有一些業(yè)務(wù)場景,我們需要手動去更新任務(wù)的觸發(fā)時間,比如某個任務(wù)是每隔10分鐘觸發(fā)一次,現(xiàn)在需要改成每隔20分鐘觸發(fā)一次,這樣既就需要手動的更新觸發(fā)器
官方的例子:
http://www.quartz-scheduler.org/documentation/quartz-2.1.x/cookbook/UpdateTrigger
Replacing a trigger 替換觸發(fā)器,通過triggerkey移除舊的觸發(fā)器,同時添加一個新的進(jìn)去。
// Define a new Trigger
Trigger trigger = newTrigger()
.withIdentity("newTrigger", "group1")
.startNow()
.build();
// tell the scheduler to remove the old trigger with the given key, and put the new one in its place
sched.rescheduleJob(triggerKey("oldTrigger", "group1"), trigger);
但是有一個地方需要注意:sched.rescheduleJob(triggerKey("oldTrigger", "group1"), trigger); 這個方法返回一個Date.
如果返回 null 說明替換失敗,原因就是舊觸發(fā)器沒有找到,所以新的觸發(fā)器也不會設(shè)置進(jìn)去.
How-To: Using Job Listeners
Quartz Scheduler 可以對Job(任務(wù))建立一個監(jiān)聽器,分別對任務(wù)執(zhí)行 之前-之后-取消 3個狀態(tài)進(jìn)行監(jiān)聽。
實現(xiàn)監(jiān)聽器需要實現(xiàn)JobListener接口,然后注冊到Scheduler上就可以了。
一:首先寫一個監(jiān)聽器實現(xiàn)類
package com.gary.operation.jobdemo.example1;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;
public class MyJobListener implements JobListener {
@Override
public String getName() {
return "MyJobListener";
}
/**
* (1)
* 任務(wù)執(zhí)行之前執(zhí)行
* Called by the Scheduler when a JobDetail is about to be executed (an associated Trigger has occurred).
*/
@Override
public void jobToBeExecuted(JobExecutionContext context) {
System.out.println("MyJobListener.jobToBeExecuted()");
}
/**
* (2)
* 這個方法正常情況下不執(zhí)行,但是如果當(dāng)TriggerListener中的vetoJobExecution方法返回true時,那么執(zhí)行這個方法.
* 需要注意的是 如果方法(2)執(zhí)行 那么(1),(3)這個倆個方法不會執(zhí)行,因為任務(wù)被終止了嘛.
* Called by the Scheduler when a JobDetail was about to be executed (an associated Trigger has occurred),
* but a TriggerListener vetoed it's execution.
*/
@Override
public void jobExecutionVetoed(JobExecutionContext context) {
System.out.println("MyJobListener.jobExecutionVetoed()");
}
/**
* (3)
* 任務(wù)執(zhí)行完成后執(zhí)行,jobException如果它不為空則說明任務(wù)在執(zhí)行過程中出現(xiàn)了異常
* Called by the Scheduler after a JobDetail has been executed, and be for the associated Trigger's triggered(xx) method has been called.
*/
@Override
public void jobWasExecuted(JobExecutionContext context,
JobExecutionException jobException) {
System.out.println("MyJobListener.jobWasExecuted()");
}
}
二:將這個監(jiān)聽器注冊到Scheduler
假設(shè)有一個任務(wù)的key是 job1與 group1
// define the job and tie it to our HelloJob class
JobDetail job = newJob(HelloJob.class)
.withIdentity("job1", "group1")
.build();
全局注冊,所有Job都會起作用
Registering A JobListener With The Scheduler To Listen To All Jobs
sched.getListenerManager().addJobListener(new MyJobListener());
指定具體的任務(wù)
Registering A JobListener With The Scheduler To Listen To A Specific Job
Matcher<JobKey> matcher = KeyMatcher.keyEquals(new JobKey("job1", "group1"));
sched.getListenerManager().addJobListener(new MyJobListener(), matcher);
指定一組任務(wù)
Registering A JobListener With The Scheduler To Listen To All Jobs In a Group
GroupMatcher<JobKey> matcher = GroupMatcher.jobGroupEquals("group1");
sched.getListenerManager().addJobListener(new MyJobListener(), matcher);
可以根據(jù)組的名字匹配開頭和結(jié)尾或包含
GroupMatcher<JobKey> matcher = GroupMatcher.groupStartsWith("g");
GroupMatcher<JobKey> matcher = GroupMatcher.groupContains("g");
sched.getListenerManager().addJobListener(new MyJobListener(), matcher);
How-To: Trigger That Executes Every Day
Using CronTrigger
trigger = newTrigger()
.withIdentity("trigger3", "group1")
.startNow()
.withSchedule(dailyAtHourAndMinute(15, 0)) // fire every day at 15:00
.build();
Using SimpleTrigger
trigger = newTrigger()
.withIdentity("trigger3", "group1")
.startAt(tomorrowAt(15, 0, 0) // first fire time 15:00:00 tomorrow
.withSchedule(simpleSchedule()
.withIntervalInHours(24) // interval is actually set at 24 hours' worth of milliseconds
.repeatForever())
.build();
Using CalendarIntervalTrigger
trigger = newTrigger()
.withIdentity("trigger3", "group1")
.startAt(tomorrowAt(15, 0, 0) // 15:00:00 tomorrow
.withSchedule(calendarIntervalSchedule()
.withIntervalInDays(1)) // interval is set in calendar days
.build();