quartz學習--定時任務實習
一個例子
package com.wz.test.code;
import org.quartz.CronScheduleBuilder;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;
public class SchedulerTest {
public static void main(String args[]) {
try {
// 建立scheduler
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// 定義一個JobDetail
JobDetail jobDetail = JobBuilder.newJob(MyJob.class)// 定義Job類為MyJob類,這是真正的執行邏輯所在
.withIdentity("job", "group")// 定義name/group
.usingJobData("name", "quartz")// 定義屬性
.build();
// 定義一個Trigger
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1") // 定義name/group
. startNow()// 一旦加入scheduler,立即生效
.withSchedule(SimpleScheduleBuilder.simpleSchedule()// 使用SimpleTrigger
.withIntervalInSeconds(5)// 每隔5秒執行一次
.repeatForever())// 一直執行,奔騰到老不停歇
.build();
JobDetail jobDetail1 = JobBuilder.newJob(MyJob1.class)// 定義Job類為MyJob1類,這是真正的執行邏輯所在
.withIdentity("job1", "group1")// 定義name/group
.usingJobData("name1", "quartz1")// 定義屬性
.build();
Trigger trigger1 = TriggerBuilder
.newTrigger()
.withSchedule(
CronScheduleBuilder.cronSchedule("0 35 17 7 2 ? 2018"))
.build();
// 加入這個排程
scheduler.scheduleJob(jobDetail, trigger);
scheduler.scheduleJob(jobDetail1, trigger1);
// 啟動
scheduler.start();
// Thread.sleep(100000);
// scheduler.shutdown(true);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
package com.wz.test.code;
import java.util.Date;
import org.quartz.Job;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public class MyJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
JobDetail detail = context.getJobDetail();
String name = detail.getJobDataMap().getString("name");
System.out.println("say hello to " + name + " at " + new Date());
}
}
這個例子很好的覆蓋了Quartz最重要的3個基本要素:
- Scheduler:排程器。所有的排程都是由它控制。
- Trigger:定義觸發的條件。例子中,它的型別是SimpleTrigger,每隔5秒中執行一次(什麼是SimpleTrigger下面會有詳述)。
- JobDetail & Job: JobDetail 定義的是任務資料,而真正的執行邏輯是在Job中,例子中是MyJob。為什麼設計成JobDetail +Job,不直接使用Job?這是因為任務是有可能併發執行,如果Scheduler直接使用Job,就會存在對同一個Job例項併發訪問的問題。而JobDetail & Job 方式,sheduler每次執行,都會根據JobDetail建立一個新的Job例項,這樣就可以規避併發訪問的問題。
關於name和group
- JobDetail和Trigger都有name和group。
- name是它們在這個sheduler裡面的唯一標識。如果我們要更新一個JobDetail定義,只需要設定一個name相同的JobDetail例項即可。
- group是一個組織單元,sheduler會提供一些對整組操作的API,比如 scheduler.resumeJobs()。
Calendar 這裡的Calendar不是jdk的java.util.Calendar,不是為了計算日期的。它的作用是在於補充Trigger的時間。可以排除或加入某一些特定的時間點。 以”每月25日零點自動還卡債“為例,我們想排除掉每年的2月25號零點這個時間點(因為有2.14,所以2月一定會破產)。這個時間,就可以用Calendar來實現。 例子:
AnnualCalendar cal = new AnnualCalendar(); //定義一個每年執行Calendar,精度為天,即不能定義到2.25號下午2:00java.util.Calendar excludeDay = new GregorianCalendar();excludeDay.setTime(newDate().inMonthOnDay(2, 25).build());cal.setDayExcluded(excludeDay, true); //設定排除2.25這個日期scheduler.addCalendar("FebCal", cal, false, false); //scheduler加入這個Calendar
//定義一個TriggerTrigger trigger = newTrigger().withIdentity("trigger1", "group1")
.startNow()//一旦加入scheduler,立即生效
.modifiedByCalendar("FebCal") //使用Calendar !!
.withSchedule(simpleSchedule()
.withIntervalInSeconds(1)
.repeatForever())
.build();
Quartz體貼地為我們提供以下幾種Calendar,注意,所有的Calendar既可以是排除,也可以是包含,取決於:
- HolidayCalendar。指定特定的日期,比如20140613。精度到天。
- DailyCalendar。指定每天的時間段(rangeStartingTime, rangeEndingTime),格式是HH:MM[:SS[:mmm]]。也就是最大精度可以到毫秒。
- WeeklyCalendar。指定每星期的星期幾,可選值比如為java.util.Calendar.SUNDAY。精度是天。
- MonthlyCalendar。指定每月的幾號。可選值為1-31。精度是天
- AnnualCalendar。 指定每年的哪一天。使用方式如上例。精度是天。
- CronCalendar。指定Cron表示式。精度取決於Cron表示式,也就是最大精度可以到秒。
JobDetail & Job
JobDetail是任務的定義,而Job是任務的執行邏輯。在JobDetail裡會引用一個Job Class定義。
public class JobTest {
public static void main(String[] args) throws SchedulerException, IOException {
JobDetail job=newJob()
.ofType(DoNothingJob.class) //引用Job Class
.withIdentity("job1", "group1") //設定name/group
.withDescription("this is a test job") //設定描述
.usingJobData("age", 18) //加入屬性到ageJobDataMap
.build();
job.getJobDataMap().put("name", "quertz"); //加入屬性name到JobDataMap
//定義一個每秒執行一次的SimpleTrigger
Trigger trigger=newTrigger()
.startNow()
.withIdentity("trigger1")
.withSchedule(simpleSchedule()
.withIntervalInSeconds(1)
.repeatForever())
.build();
Scheduler sche=StdSchedulerFactory.getDefaultScheduler();
sche.scheduleJob(job, trigger);
sche.start();
System.in.read();
sche.shutdown();
}}
public class DoNothingJob implements Job {
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println("do nothing");
}}
從上例我們可以看出,要定義一個任務,需要幹幾件事:
- 建立一個org.quartz.Job的實現類,並實現實現自己的業務邏輯。比如上面的DoNothingJob。
- 定義一個JobDetail,引用這個實現類
- 加入scheduleJob
Quartz排程一次任務,會幹如下的事:
- JobClass jobClass=JobDetail.getJobClass()
- Job jobInstance=jobClass.newInstance()。所以Job實現類,必須有一個public的無參構建方法。
- jobInstance.execute(JobExecutionContext context)。JobExecutionContext是Job執行的上下文,可以獲得Trigger、Scheduler、JobDetail的資訊。
也就是說,每次排程都會建立一個新的Job例項,這樣的好處是有些任務併發執行的時候,不存在對臨界資源的訪問問題——當然,如果需要共享JobDataMap的時候,還是存在臨界資源的併發訪問的問題。
JobDataMap
Job都次都是newInstance的例項,那我怎麼傳值給它? 比如我現在有兩個傳送郵件的任務,一個是發給"liLei",一個發給"hanmeimei",不能說我要寫兩個Job實現類LiLeiSendEmailJob和HanMeiMeiSendEmailJob。實現的辦法是通過JobDataMap。 每一個JobDetail都會有一個JobDataMap。JobDataMap本質就是一個Map的擴充套件類,只是提供了一些更便捷的方法,比如getString()之類的。 我們可以在定義JobDetail,加入屬性值,方式有二:
newJob().usingJobData("age", 18) //加入屬性到ageJobDataMap
or
job.getJobDataMap().put("name", "quertz"); //加入屬性name到JobDataMap
然後在Job中可以獲取這個JobDataMap的值,方式同樣有二:
public class HelloQuartz implements Job {
private String name;
public void execute(JobExecutionContext context) throws JobExecutionException {
JobDetail detail = context.getJobDetail();
JobDataMap map = detail.getJobDataMap(); //方法一:獲得JobDataMap
System.out.println("say hello to " + name + "[" + map.getInt("age") + "]" + " at "
+ new Date());
}
//方法二:屬性的setter方法,會將JobDataMap的屬性自動注入
public void setName(String name) {
this.name = name;
}}
對於同一個JobDetail例項,執行的多個Job例項,是共享同樣的JobDataMap,也就是說,如果你在任務裡修改了裡面的值,會對其他Job例項(併發的或者後續的)造成影響。 除了JobDetail,Trigger同樣有一個JobDataMap,共享範圍是所有使用這個Trigger的Job例項。
Job併發
Job是有可能併發執行的,比如一個任務要執行10秒中,而排程演算法是每秒中觸發1次,那麼就有可能多個任務被併發執行。 有時候我們並不想任務併發執行,比如這個任務要去”獲得資料庫中所有未傳送郵件的名單“,如果是併發執行,就需要一個數據庫鎖去避免一個數據被多次處理。這個時候一個@DisallowConcurrentExecution解決這個問題。 就是這樣
public class DoNothingJob implements Job {
@DisallowConcurrentExecution
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println("do nothing");
}}
注意,@DisallowConcurrentExecution是對JobDetail例項生效,也就是如果你定義兩個JobDetail,引用同一個Job類,是可以併發執行的。
JobExecutionException
Job.execute()方法是不允許丟擲除JobExecutionException之外的所有異常的(包括RuntimeException),所以編碼的時候,最好是try-catch住所有的Throwable,小心處理。
其他屬性
- Durability(耐久性?) 如果一個任務不是durable,那麼當沒有Trigger關聯它的時候,它就會被自動刪除。
- RequestsRecovery 如果一個任務是"requests recovery",那麼當任務執行過程非正常退出時(比如程序崩潰,機器斷電,但不包括丟擲異常這種情況),Quartz再次啟動時,會重新執行一次這個任務例項。 可以通過JobExecutionContext.isRecovering()查詢任務是否是被恢復的。
Scheduler
Scheduler就是Quartz的大腦,所有任務都是由它來設施。 Schduelr包含一個兩個重要元件: JobStore和ThreadPool。 JobStore是會來儲存執行時資訊的,包括Trigger,Schduler,JobDetail,業務鎖等。它有多種實現RAMJob(記憶體實現),JobStoreTX(JDBC,事務由Quartz管理),JobStoreCMT(JDBC,使用容器事務),ClusteredJobStore(叢集實現)、TerracottaJobStore(什麼是Terractta)。 ThreadPool就是執行緒池,Quartz有自己的執行緒池實現。所有任務的都會由執行緒池執行。
SchedulerFactory
SchdulerFactory,顧名思義就是來用建立Schduler了,有兩個實現:DirectSchedulerFactory和StdSchdulerFactory。前者可以用來在程式碼裡定製你自己的Schduler引數。後者是直接讀取classpath下的quartz.properties(不存在就都使用預設值)配置來例項化Schduler。通常來講,我們使用StdSchdulerFactory也就足夠了。 SchdulerFactory本身是支援建立RMI stub的,可以用來管理遠端的Scheduler,功能與本地一樣,可以遠端提交個Job什麼的。 DirectSchedulerFactory的建立介面
/**
* Creates a scheduler using the specified thread pool and job store. This
* scheduler can be retrieved via
* {@link DirectSchedulerFactory#getScheduler()}
*
* @param threadPool
* The thread pool for executing jobs
* @param jobStore
* The type of job store
* @throws SchedulerException
* if initialization failed
*/
public void createScheduler(ThreadPool threadPool, JobStore jobStore)
throws SchedulerException {
createScheduler(DEFAULT_SCHEDULER_NAME, DEFAULT_INSTANCE_ID,
threadPool, jobStore);
}
StdSchdulerFactory的配置例子, 更多配置,參考Quartz配置指南:
org.quartz.scheduler.instanceName = DefaultQuartzScheduler org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount = 10 org.quartz.threadPool.threadPriority = 5 org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore