定時任務調度

一、cron4j

特點:最大的特點就是小巧,簡單,功能說實話沒什麼可說的,就是模仿unix的crontab,門檻非常低,編程非常簡單. 可以執行一些簡單的定時調度功能,太複雜的還是用quartz比較好。

定時任務:

public class CronJob implements Runnable{
    public void run() {
        System.out.println("任務執行開始");
        System.out.println(new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));
        System.out.println("任務執行結束");
    }
}

調度進程:

import it.sauronsoftware.cron4j.Scheduler;
import java.text.SimpleDateFormat;
import java.util.Date;

public class TestCron {
    public static void main(String[] args) {
        CronJob cronJob = new CronJob();
        Scheduler scheduler = new Scheduler();
        scheduler.schedule("*/1 * * * *",cronJob);//每一分鐘執行一次
        System.out.println("線程開始:"+new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));
        scheduler.start();
        try {
            Thread.sleep(300000);//主線程休眠5分鐘
        }catch (InterruptedException e){

        }
        scheduler.stop();
        System.out.println("線程結束:"+new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));
    }
}

執行結果:

線程開始:2017-06-01 05:22:44
任務執行開始
2017-06-01 05:23:00
任務執行結束
任務執行開始
2017-06-01 05:24:00
任務執行結束
任務執行開始
2017-06-01 05:25:00
任務執行結束
任務執行開始
2017-06-01 05:26:00
任務執行結束
任務執行開始
2017-06-01 05:27:00
任務執行結束
線程結束:2017-06-01 05:27:44

就是這麼簡單。

二、Quartz

Quartz 可以滿足更多更復雜的調度需求,首先讓我們看看如何用 Quartz 實現每星期二 16:38 的調度安排:

清單 4. 使用 Quartz 進行任務調度

package com.ibm.scheduler;
import java.util.Date;

import org.quartz.Job;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.Scheduler;
import org.quartz.SchedulerFactory;
import org.quartz.Trigger;
import org.quartz.helpers.TriggerUtils;

public class QuartzTest implements Job {

    @Override
    //該方法實現需要執行的任務
    public void execute(JobExecutionContext arg0) throws JobExecutionException {
        System.out.println("Generating report - "
                + arg0.getJobDetail().getFullName() + ", type ="
                + arg0.getJobDetail().getJobDataMap().get("type"));
        System.out.println(new Date().toString());
    }
    public static void main(String[] args) {
        try {
            // 創建一個Scheduler
            SchedulerFactory schedFact = 
            new org.quartz.impl.StdSchedulerFactory();
            Scheduler sched = schedFact.getScheduler();
            sched.start();
            // 創建一個JobDetail,指明name,groupname,以及具體的Job類名,
            //該Job負責定義需要執行任務
            JobDetail jobDetail = new JobDetail("myJob", "myJobGroup",
                    QuartzTest.class);
            jobDetail.getJobDataMap().put("type", "FULL");
            // 創建一個每週觸發的Trigger,指明星期幾幾點幾分執行
            Trigger trigger = TriggerUtils.makeWeeklyTrigger(3, 16, 38);
            trigger.setGroup("myTriggerGroup");
            // 從當前時間的下一秒開始執行
            trigger.setStartTime(TriggerUtils.getEvenSecondDate(new Date()));
            // 指明trigger的name
            trigger.setName("myTrigger");
            // 用scheduler將JobDetail與Trigger關聯在一起,開始調度任務
            sched.scheduleJob(jobDetail, trigger);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
} 
Output:
Generating report - myJobGroup.myJob, type =FULL
Tue Feb 8 16:38:00 CST 2011
Generating report - myJobGroup.myJob, type =FULL
Tue Feb 15 16:38:00 CST 2011

清單 4 非常簡潔地實現了一個上述複雜的任務調度。Quartz 設計的核心類包括 Scheduler, Job 以及Trigger。其中,Job 負責定義需要執行的任務,Trigger 負責設置調度策略,Scheduler 將二者組裝在一起,並觸發任務開始執行。

1、Job

使用者只需要創建一個 Job 的繼承類,實現 execute 方法。JobDetail 負責封裝 Job 以及 Job 的屬性,並將其提供給 Scheduler 作爲參數。每次 Scheduler 執行任務時,首先會創建一個 Job 的實例,然後再調用 execute 方法執行。Quartz 沒有爲 Job 設計帶參數的構造函數,因此需要通過額外的 JobDataMap 來存儲 Job 的屬性。JobDataMap 可以存儲任意數量的 Key,Value 對,例如:

清單 5. 爲 JobDataMap 賦值

jobDetail.getJobDataMap().put("myDescription", "my job description"); 
jobDetail.getJobDataMap().put("myValue", 1998); 
ArrayList<String> list = new ArrayList<String>(); 
list.add("item1"); 
jobDetail.getJobDataMap().put("myArray", list);

JobDataMap 中的數據可以通過下面的方式獲取:

清單 6. 獲取 JobDataMap 的值

public class JobDataMapTest implements Job {

    @Override
    public void execute(JobExecutionContext context)
            throws JobExecutionException {
        //從context中獲取instName,groupName以及dataMap
        String instName = context.getJobDetail().getName();
        String groupName = context.getJobDetail().getGroup();
        JobDataMap dataMap = context.getJobDetail().getJobDataMap();
        //從dataMap中獲取myDescription,myValue以及myArray
        String myDescription = dataMap.getString("myDescription");
        int myValue = dataMap.getInt("myValue");
        ArrayList<String> myArray = (ArrayListlt;Strin>) dataMap.get("myArray");
        System.out.println("
                Instance =" + instName + ", group = " + groupName
                + ", description = " + myDescription + ", value =" + myValue
                + ", array item0 = " + myArray.get(0));

    }
}
Output:
Instance = myJob, group = myJobGroup, 
description = my job description, 
value =1998, array item0 = item1

2、Trigger

Trigger 的作用是設置調度策略。Quartz 設計了多種類型的 Trigger,其中最常用的是 SimpleTrigger 和 CronTrigger。

SimpleTrigger 適用於在某一特定的時間執行一次,或者在某一特定的時間以某一特定時間間隔執行多次。上述功能決定了 SimpleTrigger 的參數包括 start-time, end-time, repeat count, 以及 repeat interval。

Repeat count 取值爲大於或等於零的整數,或者常量 SimpleTrigger.REPEAT_INDEFINITELY。

Repeat interval 取值爲大於或等於零的長整型。當 Repeat interval 取值爲零並且 Repeat count 取值大於零時,將會觸發任務的併發執行。

Start-time 與 dnd-time 取值爲 java.util.Date。當同時指定 end-time 與 repeat count 時,優先考慮 end-time。一般地,可以指定 end-time,並設定 repeat count 爲 REPEAT_INDEFINITELY。

以下是 SimpleTrigger 的構造方法:

public SimpleTrigger(String name, 
                      String group, 
                      Date startTime, 
                      Date endTime, 
                      int repeatCount, 
                      long repeatInterval)

舉例如下:
創建一個立即執行且僅執行一次的 SimpleTrigger:

SimpleTrigger trigger=
new SimpleTrigger("myTrigger", "myGroup", new Date(), null, 0, 0L);

創建一個半分鐘後開始執行,且每隔一分鐘重複執行一次的 SimpleTrigger:

SimpleTrigger trigger=
new SimpleTrigger("myTrigger", "myGroup", 
   new Date(System.currentTimeMillis()+30*1000), null, 0, 60*1000);

創建一個 2011 年 6 月 1 日 8:30 開始執行,每隔一小時執行一次,一共執行一百次,一天之後截止的 SimpleTrigger:

Calendar calendar = Calendar.getInstance(); 
calendar.set(Calendar.YEAR, 2011); 
calendar.set(Calendar.MONTH, Calendar.JUNE); 
calendar.set(Calendar.DAY_OF_MONTH, 1); 
calendar.set(Calendar.HOUR, 8); 
calendar.set(Calendar.MINUTE, 30); 
calendar.set(Calendar.SECOND, 0); 
calendar.set(Calendar.MILLISECOND, 0); 
Date startTime = calendar.getTime(); 
Date endTime = new Date (calendar.getTimeInMillis() +24*60*60*1000); 
SimpleTrigger trigger=new SimpleTrigger("myTrigger", 
       "myGroup", startTime, endTime, 100, 60*60*1000);

上述最後一個例子中,同時設置了 end-time 與 repeat count,則優先考慮 end-time,總共可以執行二十四次。

CronTrigger 的用途更廣,相比基於特定時間間隔進行調度安排的 SimpleTrigger,CronTrigger 主要適用於基於日曆的調度安排。例如:每星期二的 16:38:10 執行,每月一號執行,以及更復雜的調度安排等。

CronTrigger 同樣需要指定 start-time 和 end-time,其核心在於 Cron 表達式,由七個字段組成:

Seconds
Minutes
Hours
Day-of-Month
Month
Day-of-Week
Year (Optional field)

舉例如下:
創建一個每三小時執行的 CronTrigger,且從每小時的整點開始執行:

0 0 0/3 * * ?

創建一個每十分鐘執行的 CronTrigger,且從每小時的第三分鐘開始執行:

0 3/10 * * * ?

創建一個每週一,週二,週三,週六的晚上 20:00 到 23:00,每半小時執行一次的 CronTrigger:

0 0/30 20-23 ? * MON-WED,SAT

創建一個每月最後一個週四,中午 11:30-14:30,每小時執行一次的 trigger:

0 30 11-14/1 ? * 5L

解釋一下上述例子中各符號的含義:

首先所有字段都有自己特定的取值,例如,Seconds 和 Minutes 取值爲 0 到 59,Hours 取值爲 0 到 23,Day-of-Month 取值爲 0-31, Month 取值爲 0-11,或者 JAN,FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC,Days-of-Week 取值爲 1-7 或者 SUN, MON, TUE, WED, THU, FRI, SAT。每個字段可以取單個值,多個值,或一個範圍,例如 Day-of-Week 可取值爲“MON,TUE,SAT”,“MON-FRI”或者“TUE-THU,SUN”。

通配符 * 表示該字段可接受任何可能取值。例如 Month 字段賦值 * 表示每個月,Day-of-Week 字段賦值 * 表示一週的每天。

/ 表示開始時刻與間隔時段。例如 Minutes 字段賦值 2/10 表示在一個小時內每 20 分鐘執行一次,從第 2 分鐘開始。

? 僅適用於 Day-of-Month 和 Day-of-Week。? 表示對該字段不指定特定值。適用於需要對這兩個字段中的其中一個指定值,而對另一個不指定值的情況。一般情況下,這兩個字段只需對一個賦值。

L 僅適用於 Day-of-Month 和 Day-of-Week。L 用於 Day-of-Month 表示該月最後一天。L 單獨用於 Day-of-Week 表示週六,否則表示一個月最後一個星期幾,例如 5L 或者 THUL 表示該月最後一個星期四。

W 僅適用於 Day-of-Month,表示離指定日期最近的一個工作日,例如 Day-of-Month 賦值爲 10W 表示該月離 10 號最近的一個工作日。

#僅適用於 Day-of-Week,表示該月第 XXX 個星期幾。例如 Day-of-Week 賦值爲 5#2 或者 THU#2,表示該月第二個星期四。

CronTrigger 的使用如下:

CronTrigger cronTrigger = new CronTrigger("myTrigger", "myGroup"); 
try { 
    cronTrigger.setCronExpression("0 0/30 20-13 ? * MON-WED,SAT"); 
} catch (Exception e) { 
    e.printStackTrace(); 
}

Job 與 Trigger 的鬆耦合設計是 Quartz 的一大特點,其優點在於同一個 Job 可以綁定多個不同的 Trigger,同一個 Trigger 也可以調度多個 Job,靈活性很強。

Quartz除了以上功能之外,還具有listen功能(當系統發生故障,相關人員需要被通知時,Listener 便能發揮它的作用。最常見的情況是,當任務被執行時,系統發生故障,Listener 監聽到錯誤,立即發送郵件給管理員。),數據持久化功能(即將任務調度的相關數據保存下來)。在這裏不詳細介紹了。
想要關注的可以參考:幾種任務調度的 Java 實現方法與比較


然而

Quartz雖然強大,但是實現起來過於繁瑣。好在強大的Spring已經將這一常用的Quartz整合進去,方便了我們的使用。

特別注意一點:Quartz1.*和Quartz2.*這兩個版本之間差別還是蠻大的。與Spring3.1以下版本整合必須使用Quartz1,最初我拿2.1.3的,怎麼搞都報錯:

Caused by: org.springframework.beans.factory.CannotLoadBeanClassException: Error loading class [org.springframework.scheduling.quartz.CronTriggerBean] for bean with name ‘mytrigger’ defined in class path resource [applicationContext.xml]: problem with class file or dependent class; nested exception is java.lang.IncompatibleClassChangeError: class org.springframework.scheduling.quartz.CronTriggerBean has interface org.quartz.CronTrigger as super class

查看發現spring3.0.5中org.springframework.scheduling.quartz.CronTriggerBean繼承了org.quartz.CronTrigger(public class CronTriggerBeanextends CronTrigger),而在quartz2.1.3中org.quartz.CronTrigger是個接口(publicabstract interface CronTrigger extends Trigger),而在quartz1.8.5及1.8.4中org.quartz.CronTrigger是個類(publicclass CronTrigger extends Trigger),從而造成無法在applicationContext中配置觸發器。這是spring3.1以下版本和quartz2版本不兼容的一個bug。(spring3.1以及以後版本支持quartz2)

在Spring中使用Quartz有兩種方式實現:第一種是任務類繼承QuartzJobBean,第二種則是在配置文件裏定義任務類和要執行的方法,類和方法仍然是普通類。很顯然,第二種方式遠比第一種方式來的靈活。

第一種方式的JAVA代碼:

package com.ncs.hj;  

import org.quartz.JobExecutionContext;  
import org.quartz.JobExecutionException;  
import org.springframework.scheduling.quartz.QuartzJobBean;  

public class SpringQtz extends QuartzJobBean{  
    private static int counter = 0;  
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {  
        System.out.println();  
        long ms = System.currentTimeMillis();  
        System.out.println("\t\t" + new Date(ms));  
        System.out.println(ms);  
        System.out.println("(" + counter++ + ")");  
        String s = (String) context.getMergedJobDataMap().get("service");  
        System.out.println(s);  
        System.out.println();  
    }  
} 

第二種方式的JAVA代碼:

public class InnoScheduler {

    private InnoIndexService innoIndexService;
    private InnoReviewCalenderService innoReviewCalenderService;
    private InnovationReviewService innovationReviewService;

    public void calculateResult(){
        //此處爲要定時執行的方法。
    }

    public InnoIndexService getInnoIndexService() {
        return innoIndexService;
    }

    public void setInnoIndexService(InnoIndexService innerCallbackService) {
        this.innoIndexService = innerCallbackService;
    }

    public InnoReviewCalenderService getInnoReviewCalenderService() {
        return innoReviewCalenderService;
    }

    public void setInnoReviewCalenderService(InnoReviewCalenderService innoReviewCalenderService) {
        this.innoReviewCalenderService = innoReviewCalenderService;
    }

    public InnovationReviewService getInnovationReviewService() {
        return innovationReviewService;
    }

    public void setInnovationReviewService(InnovationReviewService innovationReviewService) {
        this.innovationReviewService = innovationReviewService;
    }
}

Spring的配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd"
       xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx">


    <!-- 消息回調執行任務 -->
    <bean id="innoIndexScheduler" class="com.sf.sfpp.web.scheduler.InnoScheduler">
        <property name="innoIndexService" ref="innoIndexService"></property>
        <property name="innoReviewCalenderService" ref="innoReviewCalenderService"></property>
        <property name="innovationReviewService" ref="innovationReviewService"></property>
    </bean>

    <!-- 引用,配置要運行的方法 -->
    <bean id="calculateIndexJobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
        <property name="targetObject" >
            <ref bean="innoIndexScheduler"/>
        </property>
        <property name="concurrent" value="false" />
        <property name="targetMethod" value="calculateResult" />
    </bean>
    <!-- 引用,配置觸發任務調用時間 -->
    <bean id="calculateIndexJobTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
        <property name="jobDetail">
            <ref bean="calculateIndexJobDetail"/>
        </property>
        <property name="cronExpression">
            <!--秒 分 時 天 月  星期 年 -->
            <value>0 0 2 * * ?</value>
        </property>
    </bean>

    <!-- 每個定義的任務都要在這裏進行引用才能運行 -->
    <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <property name="triggers">
            <list>
                <ref local="calculateIndexJobTrigger" />
            </list>
        </property>
    </bean>

</beans>

可以看到配置文件裏面配置了

  • 任務bean
  • Job:MethodInvokingJobDetailFactoryBean 其中配置了關聯的任務bean,以及調用的方法。
  • Trigger:calculateIndexJobTrigger其中配置了關聯的Job,已經觸發規則cron。
  • SchedulerFactoryBean(上兩個的整合者)其中配置了關聯的Trigger。

可以看出通過xml的方式,可以靈活的配置你要調度的任務。


然而的然而

當任務沒有那麼複雜的時候,可以使用spring3的更方便的註解來實現,這個更爲簡單:

爲了調度某個方法,你只需要使用@Scheduled註解來標註它。例如,爲了讓Spring每隔24小時(86400000毫秒)觸發一個方法:

@Scheduled(fixedRate=86400000)
public void method(){
  //...
}

屬性fixedRate表名這個方法需要每隔指定的毫秒數進行週期性的調用。在本示例中,內次方法開始調用之間要經歷86400000毫秒。
如果你想要指定調用之間的間隔(也就是一次調用完成與下一次調用開始之間的間隔),那麼需要使用fixedDelay屬性。

@Scheduled(fixedDelay=86400000)
public void archiveOldSpittles(){
    //...
}

這兩種屬性在指定間隔後調用任務是很方便的。但是,爲了指定什麼時候調用方法,可以使用cron屬性。

@Scheduled(cron="0 0 0 * * SAT"public void archiveOldSpittles(){
    //....
}

當然,這種各有各的好處,基於註解這種方式可能在面對多處任務調度的時候,可能就不如配置文件來的方便。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章