1. 程式人生 > >定時任務實現方法總結與分析

定時任務實現方法總結與分析

上次熟悉了MarkDown的用法之後,由於各種原因一直沒有時間更新部落格。。。這次打算把我之前總結的一些東西陸陸續續的寫在部落格裡,希望下次用到的時候能夠快速記起來~

—————————————華麗的分割線——————————————–

1 定時任務簡介

在應用開發中,經常需要一些週期性的操作,如:需要在每天凌晨時候分析一次前一天的日誌資訊、需要每隔5分鐘檢查一下某個模組是否有異常然後自動傳送郵件給管理員,在專案執行到第30天的時候需要執行某些操作等等。這些功能需求就需要我們使用一些定時任務方法去實現,本文將介紹目前J2EE專案常用的幾種定時任務方法並比較它們的優缺點。

2 J2EE專案中常用到的三種定時任務實現方法

2.1 java.util.Timer類

2.1.1 簡介

先來介紹下Java自帶的java.util.Timer類,這個類允許你排程一個java.util.TimerTask任務。使用這種方式可以讓你的程式按照某一個頻度執行,但不能在指定時間執行。TimerTask類用於實現由Timer安排的一次或重複執行的某個任務。每一個Timer物件對應的是一個執行緒,因此計時器所執行的任務應該迅速完成,否則會延遲後續的任務執行。

java.util.Timer類方法摘要

void cancel()
終止此計時器,丟棄所有當前已安排的任務。
int purge()


從此計時器的任務佇列中移除所有已取消的任務。
void schedule(TimerTask task, Date time)
安排在指定的時間執行指定的任務。
void schedule(TimerTask task, Date firstTime, long period)
安排指定的任務在指定的時間開始進行重複的固定延遲執行。
void schedule(TimerTask task, long delay)
安排在指定延遲後執行指定的任務。
void schedule(TimerTask task, long delay, long period)

安排指定的任務從指定的延遲後開始進行重複的固定延遲執行。
void scheduleAtFixedRate(TimerTask task, Date firstTime, long period)
安排指定的任務在指定的時間開始進行重複的固定速率執行。
void scheduleAtFixedRate(TimerTask task, long delay, long period)
安排指定的任務在指定的延遲後開始進行重複的固定速率執行。

TimerTask類方法摘要

boolean cancel()
取消此計時器任務。
abstract void run()
此計時器任務要執行的操作。
long scheduledExecutionTime()
返回此任務最近實際執行的安排執行時間。

2.1.2 使用方法

使用Timer類的schedule(TimerTask task, long delay, long period)方法啟動定時器。

    Timer  timer=new Timer();  
    MyTask myTask=new MyTask();  
    timer.schedule(myTask, 1000, 2000); 

TimerTask類主要實現run()方法裡的業務邏輯,用法如下:

import java.text.SimpleDateFormat;  
import java.util.Date;  
import java.util.TimerTask;  

public class MyTask extends TimerTask {  

    @Override  
    public void run() {  
        // TODO Auto-generated method stub  
        SimpleDateFormat simpleDateFormat=null;  
        simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");  
        System.out.println("當前的系統時間為:"+simpleDateFormat.format(new Date()));  

    }  
}

2.1.3 擴充套件內容(往定時任務方法中傳引數)

import java.text.SimpleDateFormat;  
import java.util.Date;  
import java.util.TimerTask;  

public class WaitListTimerTask extends TimerTask {  
private WaitList waitList;
public WaitListTimerTask(WaitList waitList){
        this.waitList=waitList;
}

    @Override  
    public void run() {  
        // 引數waitList使用示例  
        List<CourseWaitList> allCourseWaitList = this.waitList.getAllCourseWaitList();

    }  
}

2.2 Spring3.0以後自帶的Spring-task

2.2.1 簡介

Spring3.0以後自主開發了定時任務工具spring task,可以將它比作一個輕量級的Quartz,而且使用起來很簡單,除spring相關的包外不需要額外的包,而且支援註解和配置檔案兩種形式,下面將分別介紹這兩種方式。

2.2.2 使用方法

第一種:配置檔案方式
①編寫作業類
即普通的pojo,如下:

import org.springframework.stereotype.Service;  

@Service  
public class TaskJob {  
    public void job1() {  

        System.out.println(“任務進行中。。。”);  

    }  
}

②在spring配置檔案頭中新增名稱空間及描述

<beans xmlns="http://www.springframework.org/schema/beans"  

    xmlns:task="http://www.springframework.org/schema/task"   

    …… 

    xsi:schemaLocation="http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd">

③Spring配置檔案中設定具體的任務

<task:scheduled-tasks>   

        <task:scheduled ref="taskJob" method="job1" cron="0 * * * * ?"/>   

</task:scheduled-tasks>  
<context:component-scan base-package=" com.gy.mytask " />

說明:ref引數指定的即任務類,method指定的即需要執行的方法,croncronExpression表示式,具體寫法這裡不介紹了,詳情見附錄。
這個配置根據專案實際情況調整包的位置,spring掃描註解用的。

第二種:使用註解形式
①編寫作業類
即普通的pojo,如下:

import org.springframework.scheduling.annotation.Scheduled;    
import org.springframework.stereotype.Component;  



@Component(“taskJob”)  

public class TaskJob {  

    @Scheduled(cron = "0 0 3 * * ?")  

    public void job1() {  

        System.out.println(“任務進行中。。。”);  

    }  

}  

注意:此處@Schedule註解有三個方法或者叫引數,分別表示的意思是:
cron:指定cron表示式
fixedDelay:即表示從上一個任務完成開始到下一個任務開始的間隔,單位是毫秒。
fixedRate:即從上一個任務開始到下一個任務開始的間隔,單位是毫秒。

②新增task相關的配置

<?xml version="1.0" encoding="UTF-8"?>  
<beans 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:context="http://www.springframework.org/schema/context"  
    xmlns:tx="http://www.springframework.org/schema/tx"  
    xmlns:task="http://www.springframework.org/schema/task"  
    xsi:schemaLocation="  
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd  
        http://www.springframework.org/schema/context   
http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd  
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd  
        http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd"  
    default-lazy-init="false">  

    <context:annotation-config />  

    <!—spring掃描註解的配置   -->  
<context:component-scan base-package="com.gy.mytask" />  

<!—開啟這個配置,spring才能識別@Scheduled註解   -->  
    <task:annotation-driven scheduler="qbScheduler" mode="proxy"/>  

    <task:scheduler id="qbScheduler" pool-size="10"/>  

說明:理論上只需要加上《task:annotation-driven /》這句配置就可以了,這些引數都不是必須的。

2.3 定時任務框架Quartz

2.3.1 簡介

Quartz是OpenSymphony開源組織在Job scheduling領域又一個開源專案,它可以與J2EE與J2SE應用程式相結合也可以單獨使用。Quartz可以用來建立簡單或為執行十個,百個,甚至是好幾萬個Jobs這樣複雜的程式。Jobs可以做成標準的Java元件或 EJBs。Quartz的最新版本為Quartz 2.2.2。下面將介紹兩種Quartz使用方式:

2.3.2 使用方法

第一種:作業類繼承自特定的基類
org.springframework.scheduling.quartz.QuartzJobBean
①編寫作業類

import org.quartz.JobExecutionContext;  
import org.quartz.JobExecutionException;  
import org.springframework.scheduling.quartz.QuartzJobBean;  
public class Job1 extends QuartzJobBean {  

    private int timeout;  
    private static int i = 0;  
    //排程工廠例項化後,經過timeout時間開始執行排程  
    public void setTimeout(int timeout) {  
        this.timeout = timeout;  
    }  

    /** 
    * 要排程的具體任務 
    */  
    @Override  
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {  
          System.out.println("定時任務執行中…");  
    }  
}

②spring配置檔案中配置作業類JobDetailBean

<bean name="job1" class="org.springframework.scheduling.quartz.JobDetailBean">  
    <property name="jobClass" value="com.gy.Job1" />  
    <property name="jobDataAsMap">  
        <map>  
            <entry key="timeout" value="0" />  
        </map>  
    </property>  
</bean> 

說明:org.springframework.scheduling.quartz.JobDetailBean有兩個屬性,jobClass屬性即我們在java程式碼中定義的任務類,jobDataAsMap屬性即該任務類中需要注入的屬性值。

③配置作業排程的觸發方式(觸發器)
Quartz的作業觸發器有兩種,分別是
org.springframework.scheduling.quartz.SimpleTriggerBean
org.springframework.scheduling.quartz.CronTriggerBean
第一種SimpleTriggerBean,只支援按照一定頻度呼叫任務,如每隔30分鐘執行一次。
配置方式如下:

<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">  
    <property name="jobDetail" ref="job1" />  
    <!-- 排程工廠例項化後,經過0秒開始執行排程 -->
    <property name="startDelay" value="0" />  
    <!-- 每2秒排程一次 --> 
    <property name="repeatInterval" value="2000" /> 
</bean> 

第二種CronTriggerBean,支援到指定時間執行一次,如每天12:00執行一次等。
配置方式如下:

<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">  
    <property name="jobDetail" ref="job1" />  
    <!—每天12:00執行一次 -->  
    <property name="cronExpression" value="0 0 12 * * ?" />  
</bean> 

④配置排程工廠

<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">  
    <property name="triggers">  
        <list>  
            <ref bean="cronTrigger" />  
        </list>  
    </property>  
</bean> 

說明:該引數指定的就是之前配置的觸發器的名字。

⑤啟動你的應用即可,即將工程部署至tomcat或其他容器。

第二種:作業類不繼承特定基類(推薦使用)
Spring能夠支援這種方式,歸功於兩個類:
org.springframework.scheduling.timer.MethodInvokingTimerTaskFactoryBean
org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean
這兩個類分別對應spring支援的兩種實現任務排程的方式,即前文提到到java自帶的timer task方式和Quartz方式。這裡我只寫MethodInvokingJobDetailFactoryBean的用法,使用該類的好處是,我們的任務類不再需要繼承自任何類,而是普通的pojo。
①編寫作業類(普通POJO)

public class Job2 {  
public void doJob2() {  
    System.out.println("不繼承QuartzJobBean方式-排程進行中...");  
    }  
}  

②配置作業類

<bean id="job2"  
class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">  
    <property name="targetObject">  
        <bean class="com.gy.Job2" />  
    </property>  
    <property name="targetMethod" value="doJob2" />  
    <!-- 作業不併發排程 -->  
    <property name="concurrent" value="false" />
</bean> 

說明:這一步是關鍵步驟,宣告一個MethodInvokingJobDetailFactoryBean,有兩個關鍵屬性:targetObject指定任務類,targetMethod指定執行的方法。

③配置作業排程的觸發方式(觸發器)
Quartz的作業觸發器有兩種,分別是
org.springframework.scheduling.quartz.SimpleTriggerBean
org.springframework.scheduling.quartz.CronTriggerBean
第一種SimpleTriggerBean,只支援按照一定頻度呼叫任務,如每隔30分鐘執行一次。
配置方式如下:

<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">  
    <property name="jobDetail" ref="job1" />
    <!-- 排程工廠例項化後,經過0秒開始執行排程 -->    
    <property name="startDelay" value="0" />
    <!-- 每2秒排程一次 -->  
    <property name="repeatInterval" value="2000" />
</bean> 

第二種CronTriggerBean,支援到指定時間執行一次,如每天12:00執行一次等。
配置方式如下:

<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">  
    <property name="jobDetail" ref="job1" />  
    <!—每天12:00執行一次 -->  
    <property name="cronExpression" value="0 0 12 * * ?" />  
</bean> 

④配置排程工廠

<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">  
    <property name="triggers">  
        <list>  
            <ref bean="cronTrigger" />  
        </list>  
    </property>  
</bean>

說明:該引數指定的就是之前配置的觸發器的名字。
⑤啟動你的應用即可,即將工程部署至tomcat或其他容器。

2.2.3 擴充套件內容

①配置多個定時器任務

<util:properties id="applicationProps" location="classpath:enroll.properties" />
    <context:property-placeholder properties-ref="applicationProps" />

    <bean id="waitListExpireJob" class="com.yunteng.ngtl.enroll.tool.waitListExpireTaskJob" />
    <bean id="expireJobTask"
        class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
        <property name="targetObject" ref="waitListExpireJob" />
        <property name="targetMethod" value="expireProcess" />
    </bean>
    <bean id="waitListExpireTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
        <property name="name" value="waitListExpireTriggerName" />
        <property name="group" value="waitListExpireTriggerGroup" />
        <property name="jobDetail">
            <ref bean="expireJobTask" />
        </property>
        <property name="cronExpression">
            <value>#{applicationProps['cron.expireTask']}</value>
        </property>
    </bean>

    <bean id="waitListObserveJob" class="com.yunteng.ngtl.enroll.tool.waitListObserveTaskJob" />
    <bean id="observeJobTask"
        class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
        <property name="targetObject" ref="waitListObserveJob" />
        <property name="targetMethod" value="observeProcess" />
    </bean>
    <bean id="waitListObserveTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
        <property name="name" value="waitListObserveTriggerName" />
        <property name="group" value="waitListObserveTriggerGroup" />
        <property name="jobDetail">
            <ref bean="observeJobTask" />
        </property>
        <property name="cronExpression">
            <value>#{applicationProps['cron.observeTask']}</value>
        </property>
    </bean>

    <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
       <property name="triggers">
         <list>
           <ref local="waitListExpireTrigger" />
           <ref local="waitListObserveTrigger" />
         </list>
       </property>
     </bean>

②動態修改定時器任務排程時間週期(關鍵字:quartz change cron expression runtime)
注意:目前只在Quartz1.8.6版本下測試成功

CronTrigger cronTrigger = (CronTrigger) stdScheduler.getTrigger(triggerName,triggerGroupName);
cronTrigger.setCronExpression(newCronExpression);
stdScheduler.rescheduleJob(triggerName,triggerGroupName,cronTrigger);

3 總結與分析

~ Timer Spring-Task Quartz
作業類的繼承方式 java.util.Timer中需要繼承自java.util.TimerTask 普通的java類,不需要繼承其他類 繼承自org.springframework.scheduling.quartz.QuartzJobBean
是否可以使用Cron表示式 不可以 可以 可以
動態改變執行時間週期 可以,但是使用不靈活 資料太少,未找到相關方法 可以,將觸發器重新啟動即可重新排程任務(資料較多,目前只在Quartz1.8.6版本測試通過)
使用難易程度 簡單 簡單 稍難,需要配置的部分相對較多且繁瑣

這篇博文到這裡就結束了,希望下次有時間可以多新增一些圖表等更加形象的內容。