1. 程式人生 > >quartz學習--定時任務實習

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");
}}

從上例我們可以看出,要定義一個任務,需要幹幾件事:

  1. 建立一個org.quartz.Job的實現類,並實現實現自己的業務邏輯。比如上面的DoNothingJob。
  2. 定義一個JobDetail,引用這個實現類
  3. 加入scheduleJob

Quartz排程一次任務,會幹如下的事:

  1. JobClass jobClass=JobDetail.getJobClass()
  2. Job jobInstance=jobClass.newInstance()。所以Job實現類,必須有一個public的無參構建方法。
  3. 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