一個Java後臺程序的實例

一個JAVA後臺程序的設計方案     選擇自 glchengang 的 Blog  
關鍵字   java.util.Timer 定時器 後臺
出處    

作者:陳剛,桂林人,97年畢業於廣西師範大學數學系,暫於IBM中國研究中心兼職從事軟件開發(2004.2-?),專注基於java平臺的軟件開發。
email:  [email protected]
blog:  glchengang.yeah.net

      很多系統都需要一個在後臺不間斷運行的程序,以定期執行某些系統任務。這類似於Windows中自帶的計劃任務的功能。我一年半前我參與某省聯通的網管項目的開發,曾經寫過一個這樣的後臺程序,它要不間隔的從各種類型服務器上,下載各種類型的數據文件(每個文件都有幾兆大小),並將這些文件解讀成一條條記錄插入到數據庫中。這個後臺程序直接使用java中的線程,由於線程的複雜性,調試也困難,很不幸這個後臺程序很不穩定,每週都會有一兩次會停在那裏不再往下執行,原因至今天未找到,成爲我心中永遠的痛。

      時隔今日,再次有幸參與IBM一個開發項目,這個項目同樣需要一個類似的後臺運行程序,這個程序的任務是:每隔一天檢查數據庫中的數據,並對符合某些條件記錄進行某操作。任務很簡單,爲了今後擴展方便,我將這個設計成了一個多任務可管理的後臺程序。周未我設置了兩個任務同時執行,一任務每10秒執行一次,另一任務每1秒執行一行,運行了兩天,運行較果良好。估計很多朋友會面臨與我同樣的問題,在此將程序思路和代碼公佈,希望有興趣的朋友大家一起研究探討。

一、程序運行界面:

1、總控制檯



2、計劃任務的設置界面。
點擊查看原圖片


3、控制檯輸出結果。


二、程序開發環境:
       使用Java(JDK 1.4)開發,圖形界面使用Eclipse (2.1.3版)的SWT方式開發。運行主機:P4 2.6+1G內存 windowsXP操作系統

三、預備。
       開發此類程序,最好不要直接使用JAVA的線程來編程,這樣會增加不必要的複雜度和難度,吃力不討好。在JAVA中有一個包 java.util.Timer 這個包封裝了對線程的操作,我們可以把它稱做定時器類。我們先來看一個簡單例子:
import java.util.Timer;
import java.util.TimerTask;
public class Reminder {
    Timer timer;
    public Reminder(int seconds) {
        timer = new Timer();
        timer.schedule(new RemindTask(),seconds*1000); //參數要求轉化成毫秒
    }
    public static void main(String args[]) {
        new Reminder(5); //5秒後運行
    }
    /**一個內部類,封裝了所要運行的任務*/
    class RemindTask extends TimerTask {
        public void run() {
            System.out.println("任務運行。。。。");
            timer.cancel(); //結束timer中的所有任務
        }
    }
}

  這裏涉及於兩個JAVA類Timer和TimerTask。我們繼承TimerTask類後,將所要運行的任務封裝其run方法中;Timer可以管理幾千個任務(TimerTask),注意,同一個任務對象不能兩次加入到Timer中執行。
對(雖然執行的任務都一樣,但是兩個任務對象):
timer.schedule(new RemindTask(), seconds * 1000);
timer.schedule(new RemindTask(), seconds * 1000);


RemindTask task= new RemindTask();
timer.schedule(task, seconds * 1000);
timer.schedule(task, seconds * 2000);

四、設計方案。

主要的類圖


說明:
  任務類的設計。我們先創建一個抽象類AbstractTimerTask,這個類直接繼承至TimerTask類,提供對TimerTask封裝。然後所有具體的任務類(如:TimerTask_1)繼承自AbstractTimerTask。
import java.util.TimerTask;
public abstract class AbstractTimerTask extends TimerTask {
    TaskEntry taskEntry; //任務記錄
    public AbstractTimerTask(TaskEntry taskEntry) {
        this.taskEntry = taskEntry;
    }
    /*
     * 生成一個新的實例相當於克隆自身;原因在於:同一任務對象不能兩次加入到Timer
     * 在TaskEntry類將看到它的使用方法
     */
    abstract AbstractTimerTask getCloneObject();
}


下面是它的一個實現類的源代碼我們可以將要運行任務的代碼寫在這個類中。
import java.util.Calendar;
public class TimerTask_1 extends AbstractTimerTask {
    public TimerTask_1(TaskEntry taskEntry) { //構造方法
        super(taskEntry);
    }
    public AbstractTimerTask getCloneObject() {  
        return new TimerTask_1(taskEntry);
    }
    public void run() {
        /*在這裏寫你要執行的程序。。。。。*/
        System.out.println("??時:"+taskEntry.getName()+"運行了一次");
        this.taskEntry.taskStart(); //運行下一個時間點任務
    }
}



在AbstractTimerTask類有一個TaskEntry字段,這是本設計的一個核心類,它代表一條封裝完整的任務記錄,每個任務類和它的運行計劃都封裝在這條類中,源代碼如下。Timer和AbstractTimerTask前面都已說過,那麼TimePlan是做什麼用的呢?
import java.util.Calendar;
import java.util.Date;
import java.util.Timer;
import mytimer.util.Util;
/**任務記錄類*/
public class TaskEntry {
    public static final int TASK_START = 0; //定義兩個表示任務記錄狀態常量
    public static final int TASK_STOP = 1;
    private Long oid; //任務ID號,唯一
    private String name; //任務名稱
    private int state = TASK_STOP; //任務狀態(啓動/停止)
    private Timer timer; //JAVA計時器
    private TimePlan timePlan; //時間計劃的類型
    private AbstractTimerTask timerTask; //任務類的種子對象,由這個對象來不斷克隆
    private AbstractTimerTask runTimerTask; //運行計劃的當前任務
    /**
       * ITaskEntry.taskStart()-->TimerTask.run()-->ITaskEntry.taskStart()
       * 形成一個循環迴路。本方法負責起動本類代表的任務
       */
    public void taskStart() {
        if (timePlan.haveNext()) {
            Date date = timePlan.nextDate();//得到任務計劃時間
            runTimerTask = timerTask.getCloneObject();//得到任務(複製)
            timer.schedule(runTimerTask, date); //加入計劃隊列
            //打印將要運行的計劃任務的信息
            Calendar c = Calendar.getInstance();
            c.setTimeInMillis(runTimerTask.scheduledExecutionTime());
            System.out.println(Util.dateToLongStr(c.getTime())+"將運行"+name);
        } else {
            state = TASK_STOP;
            System.out.println(name + "結束");
        }
    }
     /**停止任務*/
    public void taskStop() {
        if (runTimerTask != null) {
            //打印信息
            Calendar c = Calendar.getInstance();
            c.setTimeInMillis(runTimerTask.scheduledExecutionTime());
            System.out.println("計劃於:"+Util.dateToLongStr(c.getTime())+"運行的" + name + "被終止");
            //終止本任務, 調用Timer.cancel()是終止Timer的所有任務。
            runTimerTask.cancel();
        } else {
            System.out.println(name + "未進入執行計劃");
        }
    }
 ……… 一些屬性的get/set方法(省略)
    /** 監聽類(內部類) */
    public static class DateBeforeTodayException extends NullPointerException {
        private Date date;
        public DateBeforeTodayException(Date date) {this.date = date;}
        public String toString() {
            return "計劃時間(" + Util.dateToLongStr(date) + ")早於當前時間";
        }
    }
}



1、TimePlan是一個接口(interface),它是表示“運行計劃的方案”,這個程序中提供了三種運行計劃方案(見前圖:計劃任務的設置界面):
一次性運行。
每隔一個時間段運行。
一週中選擇那幾天運行。 
將它設計成一個接口是爲了方便今後擴展,如果要新增新的時間方案只需要繼承這個接口寫一個新的實現即可。三種時間方案的類圖如下:


說明:
a) TimePlan封裝了五個方法,其它haveNext()和nextDate()最重要,這個兩個方法模仿了Java中集合類(Collection)的迭代器(Iterator)的設計形式,代碼如下:
import java.util.Date;
//時間計劃方案的接口
public interface TimePlan {
boolean haveNext();//判斷還有沒有下一個計劃時間
    Date nextDate();//得到下一個計劃時間
    Date getCurrentDate();//得到開始時間
    void setCurrentDate(Date date); //設計開始時間
    String getTimePlanString();//顯示運行計劃方案的文字說明
}

b) AbstractTimePlan是這個抽象類,主要目的是將一些各子類的公共方法寫在這裏。代碼如下:
import java.util.Date;
public abstract class AbstractTimePlan implements TimePlan {
    //記錄計劃的第一時間點,除設置新的起始時間,否則不再改變
  protected Date currentDate;
/*
當前計劃的時間點,每次計劃替換時被更新,
似乎這個才應叫cureentDate,sorry不想再改了
*/
  protected Date planDate;

  public boolean haveNext() {
      return (planDate != null);
  }
  public Date getCurrentDate() {
      return currentDate;
  }
  public void setCurrentDate(Date date) {
      currentDate = date;
      planDate = date; //在賦給currentDate值時,同時也賦給planDate
  }
}

c) 然後我們看看三種計劃方案的實現類的源代碼:
//“一次性運行”的計劃方案類
import java.util.Date;
public class TimePlanOnce extends AbstractTimePlan {
    public Date nextDate() {
//把要當前的計劃時間保存在中間變量中
        Date returnDate = this.planDate;
//算出下一個計劃時間。沒有下一個就設爲null
        this.planDate = null;
    //判斷一下計劃時間合不合條件
        if (returnDate == null)
            throw new NullPointerException("沒有下一個計劃日期");
        return returnDate;
    }

    public String getTimePlanString() {
        return "一次性運行,運行時間: (打印this.currentDate) ";
    }
}

//“週期性間隔”的時間計劃方案類
import java.util.Date;
public class TimePlanPeriod extends AbstractTimePlan {
public static final int HOUR = 0;
public static final int DAY = 1;

private int spaceTime; //間隔時間,單位毫秒
private int timeType;

public Date nextDate() {
//把要當前的計劃時間保存在中間變量中
     Date returnDate = this.planDate;
//算出下一個計劃時間。沒有下一個就設爲null
     int milliSecond = 0;
     if (timeType ==HOUR) milliSecond = spaceTime * 1000; //小時*60*60*1000;
     if (timeType ==DAY) milliSecond = spaceTime * 24 * 60 * 60 * 1000; //天
     planDate = Util.DateAddSpaceMilliSecond(planDate, milliSecond);
     //判斷一下計劃時間合不合條件
     if (returnDate == null)
         throw new NullPointerException("沒有下一個計劃日期");
     return returnDate;
}
public String getTimePlanString() {
     if (timeType == HOUR)
         return "第一次運行於:currentDate.並每隔spaceTime小時運行一次";
     if (timeType == DAY)
         return "第一次運行於:currentDate.並每隔spaceTime天運行一次";
     return "";
}

public int getSpaceTime() {    return spaceTime; }
public int getTimeType() { return timeType;   }
public void setSpaceTime(int i) { spaceTime = i; }
public void setTimeType(int i) {  timeType = i; }
}


/**選擇一週的某幾天,讓這幾天在同一時間點運行任務, 一週內必須選擇一天*/
import java.util.Calendar;
import java.util.Date;
public class TimePlanSelectWeek extends AbstractTimePlan {
   private static Calendar c = Calendar.getInstance(); //取得一個日曆實例
   private static int spaceMilliSecond = 0; //間隔時間,單位毫秒
   private boolean[] selectWeek = new boolean[7]; //0爲星期日 ,1爲星期一

   public Date nextDate() {
       Date returnDate = null;
if (!isSelectWeek(planDate)) //如果這一天不是所選週中的一天
           planDate = getNextDate(planDate);
       returnDate = planDate;
       planDate = getNextDate(planDate);
//判斷一下計劃時間合不合條件
       if (returnDate == null)
           throw new NullPointerException("沒有下一個計劃日期");
       return returnDate;
   }
//算出下一個計劃時間。沒有下一個就設爲null
   private Date getNextDate(Date date) {
       Date tempDate = date;
       Date returnDate = null;
       for (int i = 0; i < 7; i++) {
           tempDate = Util.DateAddSpaceMilliSecond(tempDate, spaceMilliSecond);
           if (isSelectWeek(tempDate)) {
               returnDate = tempDate;
               break;
           }
       }
       return returnDate;
   }

   /**設置某星期是否被選, 0爲星期日 ,1爲星期一....6爲星期六*/
   public void setSelectWeek(int i, boolean b) {selectWeek[i] = b;}
   /** 判斷某星期是否被選*/
   public boolean isSelectWeek(int i) {return selectWeek[i];}
   /**判斷某天所屬星期幾是否被選*/
   public boolean isSelectWeek(Date date) {
       if (date == null) return false;
       c.setTime(date);
       //Calendar.DAY_OF_WEEK:星期日=1,星期六=7 c.get(Calendar.DAY_OF_WEEK)
       return isSelectWeek(c.get(Calendar.DAY_OF_WEEK) - 1);
   }
   public String getTimePlanString() {
       StringBuffer sb = new StringBuffer("");
       if (selectWeek[1]) sb.append("週一,");
       if (selectWeek[2]) sb.append("週二,");
       if (selectWeek[3]) sb.append("週三,");
       if (selectWeek[4]) sb.append("週四,");
       if (selectWeek[5]) sb.append("週五,");
       if (selectWeek[6]) sb.append("週六,");
       if (selectWeek[0]) sb.append("週日,");
       return "每週的"+sb.toString()+"運行";
   }
}



TimerTask的工廠類。將生成TimerTask的代碼另起一個類的好處是代碼的層次比較清楚,也比較好管理。由於TimerTask包含有幾個字段,因此產生一個TimerTask對象還是有一定的複雜度,建立一個專門生成TimerTask的工廠類,這樣我們在生成一個TimerTask對象時就可以少掉很多麻煩的代碼了。當然由於我的工作任務,只需要一個TimerTask對象就夠了,所以最初之前我是將它直接寫在圖形界面的代碼裏的。

  這裏建立一個TimerTask對象池tasks,它是一個靜態變量,這樣在getInstance時不必總是要新生成一個TimerTask。還有Timer也是一個靜態變量,它是一個全局單例(是最簡單的單例模式了),因爲前面說了Timer可以管理幾千個任務,所以Timer對象一個就夠了。
import java.util.HashMap;
import java.util.Timer;
public class TaskEntryFactory {
    private static final HashMap tasks = new HashMap();
    private static final Timer timer = new Timer();
    public static TaskEntry getInstance(Long oid, String name) {
        if (tasks.containsKey(oid)) {
            return (TaskEntry) tasks.get(oid);
        } else {
            TaskEntry entry = new TaskEntry();
            entry.setOid(oid);
            entry.setName(name);
            entry.setTimer(timer);
            entry.setTimerTask(new TimerTask_1(entry));
            tasks.put(oid, entry);
            return entry;
        }
    }
}



起動和停止任務,當“任務設置界面(TaskListDialog.java)”點擊OK後處理。界面的編寫就不在本文討論的範圍內了。
//任務設置界面中點擊”確認(OK)”按鈕後的處理
if (dialog.open() == Window.OK) {
    if (taskEntry.getState() == TaskEntry.TASK_START) {
        taskEntry.taskStop();//將舊的停掉
        taskEntry.taskStart();//開始新設置的
    }
if (taskEntry.getState() == TaskEntry.TASK_STOP)
        taskEntry.taskStop();
    tv.refresh(taskEntry);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章