java定時任務

定時任務的Java實現

就是計劃任務啦,只是在項目中這樣叫也就習慣了,參考項目中大神的實現。
目的:通過MySQL配置,可以從MySQL中讀取參數,按時定時啓動和關閉。數據庫記錄字段包括實現類名(默認爲jobName、jobGroupName、triggerName、triggerGroupName),創建時間,調度規則(cron表達式),啓動標誌,啓動參數。
實現:quartz,與Strus2管理Action的思路相似(ActionMapping->ActionProxy->Action)。對於多節點的考慮,也是通過對數據庫記錄的查詢、添加、刪除實現Lock(數據庫記錄是唯一的)。

相關的類有5個

備註
JobTimerController 適配原有項目結構,啓動調度器執行定時任務
JobScheduler 靜態調度器類,管理Job列表,對Job的CQUD操作
JobProxy IJob的代理類,每個任務對應一個線程實例,執行定時任務
JobLock 靜態鎖,防止多節點異步執行任務,基於數據庫實現
IJob 函數接口,定時任務需要基於此實現

類之間的關係如下

這裏寫圖片描述

下面說說大體思路如何實現

1.定時任務調度器啓動類JobTimerController
注意resultList、jobList只是保存Job的description(Map形式),但resultList是初始啓動配置,jobList是內存中正運行的任務,而JobScheduler纔是實際對IJob子類(相關任務實例)的操作(啓動中斷等)。因此,當數據庫中添加了新的定時任務時,需要啓動該類通知調度器類JobScheduler執行新的任務(本身該類該類也要當作定時任務執行才合理嘛)。

public void work(){
    List resultList = /* 從數據庫中獲取任務 */
    ArrayList jobList = JobScheduler.getJobList();

    // 比較resultList與jobList
    //調度器已經運行在內存中且已已有任務在執行
    if (!ArrayUtils.isEmpty(jobList.toArray())){
            for(int i=0;i<jobList.size();i++){
                jobMap = (HashMap) jobList.get(i);
                String jobName = jobMap.get("jobName").toString();
                //resultList不存在,數據庫中無記錄則移除任務
                if(!exist(jobName)){
                    //從quartz中刪除,調用Scheduler.deleteJob
                    JobScheduler.removeJob(jobMap);
                    //從調度器JobScheduler中刪除
                    jobList.remove(jobMap);
                    i--;
                }
            }
    }

    //配置好在數據庫中,啓動新的任務
    if (!ArrayUtils.isEmpty(resultList.toArray())){
        for(int i=0;i<resultList.size();i++){
            jobMap = (HashMap) resultList.get(i);
            String jobName = jobMap.get("jobName").toString();

            //不在調度器中,則新建任務實例並啓動執行
            if(!exist(jobName)){
                //添加到調度器JobScheduler
                jobList.add(jobMap);
                //調用Scheduler.scheduleJob啓動定時任務
                JobScheduler.addJob(jobMap);
            }
            //已存在則校驗啓動標誌等
            else{
                if( "Y".equals(jobMap.get("jobStart").toString()) )
                    JobScheduler.updateJob(jobMap);
            }
        }
    }
}

2.定時任務調度器類JobSchduler
封裝了quartz的操作。

    private static SchedulerFactory sf = new StdSchedulerFactory();

    public static void addJob(HashMap jobMap) throws SchedulerException, ParseException, ClassNotFoundException, InstantiationException, IllegalAccessException {
        String jobName = jobMap.get("jobName").toString();
        String jobGroupName = jobMap.get("jobGroupName").toString();
        String triggerName = jobMap.get("triggerName").toString();
        String triggerGroupName = jobMap.get("triggerGroupName").toString();
        String jobClass = jobMap.get("jobBussinessClass").toString();
        String time = jobMap.get("cronExpression").toString();
        Class tstxJobClass =  Class.forName(baseTaskClassName);
        Job tstxJob = (Job) tstxJobClass.newInstance();

        Scheduler sched = sf.getScheduler();
        JobDetail jobDetail = new JobDetail(jobName, jobGroupName,tstxJob.getClass());
        jobDetail.getJobDataMap().put("jobMap", jobMap);
        CronTrigger trigger = new CronTrigger(triggerName,triggerGroupName);
        trigger.setCronExpression(time);
        sched.scheduleJob(jobDetail,trigger);
        //啓動
        if(!sched.isShutdown()){
            sched.start();
        }
    }

    public static void updateJob(HashMap jobMap) throws SchedulerException, ParseException {
        String triggerName = jobMap.get("triggerName").toString();
        String triggerGroupName = jobMap.get("triggerGroupName").toString();
        String time = jobMap.get("cronExpression").toString();

        Scheduler sched = sf.getScheduler();
        Trigger trigger = sched.getTrigger(triggerName, triggerGroupName);
        if(trigger!=null){
            CronTrigger ct = (CronTrigger) trigger;
            // 修改時間
            ct.setCronExpression(time);
            // 重啓觸發器
            sched.rescheduleJob(triggerName, triggerGroupName , ct);
        }
    }

    public static void removeJob(HashMap jobMap) throws SchedulerException{
        String jobName = jobMap.get("jobName").toString();
        String jobGroupName = jobMap.get("jobGroupName").toString();
        String triggerName = jobMap.get("triggerName").toString();
        String triggerGroupName = jobMap.get("triggerGroupName").toString();

        Scheduler sched = sf.getScheduler();
        sched.pauseTrigger(triggerName, triggerGroupName);//停止觸發器
        sched.unscheduleJob(triggerName, triggerGroupName);//移除觸發器
        sched.deleteJob(jobName, jobGroupName);
    }

    public static void interruptJob(HashMap jobMap) throws SchedulerException{
        String jobName = jobMap.get("jobName").toString();
        String jobGroupName = jobMap.get("jobGroupName").toString();
        String triggerName = jobMap.get("triggerName").toString();
        String triggerGroupName = jobMap.get("triggerGroupName").toString();

        Scheduler sched = sf.getScheduler();
        sched.interrupt(triggerName, triggerGroupName);//中斷觸發器
    }

3.定時任務代理類JobProxy
implements InterruptableJob,新開線程執行具體的定時任務

    @Override
    public void interrupt() throws UnableToInterruptJobException {
        _interrupted = true;
    }


    @Override
    public void execute(final JobExecutionContext context) throws JobExecutionException {

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {

                JobDetail jobDetail = context.getJobDetail();
                HashMap jobMap = (HashMap)jobDetail.getJobDataMap().get("jobMap");

                //鎖定任務
                JobLock.Lock(jobMap);

                //任務正在執行中, 中斷當前線程任務..
                if (_interrupted) {
                    return;
                }

                try{
                    //創建具體的任務實現類並執行任務
                    String className = (String)jobMap.get("jobClass");
                    IBaseJob baseJob = Class.forName(className).newInstance();
                    baseJob.execute(context);
                }catch (JobExecutionException e){
                    /* 顯示異常,打印日誌 */
                }

                //解鎖
                finally {
                    JobLock.unLock(jobMap);
                }
            }
        });
        thread.start();
    }

4.併發鎖JobLock
基於數據庫的實現,需要達到鎖的效果時,寫入相關信息到數據庫記錄,當數據庫中存在相應記錄時,則說明該任務已鎖,其中lockJobName爲PK。

    public synchronized static void lock(HashMap jobMap){
        //解析定時任務參數信息
        String jobName = jobMap.get("jobName").toString();//任務名稱
        String jobGroupName = jobMap.get("jobGroupName").toString();//任務組名
        String lockJobName = "LOCK_" + jobName + "_" + jobGroupName;//redis鎖定job名稱

        HashMap lockJobMap = /* 通過lockJobName從數據庫中獲取Lock信息 */

        //未鎖
        if (null == lockJobMap){
            //嘗試將任務鎖定
            boolean lock = /* 將lockJobName寫入到數據庫中 */
            if (!lock){
                //鎖定失敗, 不可執行定時任務
                JobSchduler.interruptJob(jobMap);
                return;
            }
        }

        //已鎖,需判斷定時任務鎖定(過期)時間
        else{
            String jobStartTimeStr = lockJobMap.get("jobStartTime").toString();//任務執行時間
            String lockTime = StringUtils.isEmpty(jobMap.get("lockTime").toString()) ? "600" : jobMap.get("lockTime").toString();//鎖定秒數
            Date jobStartTimeData = null;
            try {
                jobStartTimeData = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(jobStartTimeStr);//轉換日期類型
            } catch (ParseException e) {
                e.printStackTrace();
            }
            Integer lockTimeInt = Integer.parseInt(lockTime);//計算鎖定時長
            Date jobExceedDate = DateUtils.addSeconds(jobStartTimeData,lockTimeInt);//計算JOB過期時間
            Boolean isJobExceed = jobExceedDate.before(new Date());

            //鎖定時間(開始時間+鎖定時長)與當前時間比較
            //過期不可執行任務
            if(!isJobExceed){
                JobSchduler.interruptJob(jobMap);
            }
            //未過期,重鎖
            else{
                unlock(jobMap);
                //嘗試將任務鎖定
                boolean lock = /* 將lockJobName寫入到數據庫中 */
                if (!lock){
                    //鎖定失敗, 不可執行定時任務
                    JobSchduler.interruptJob(jobMap);
                    return;
                }
            }
        }
    }

    public synchronized static void unlock(HashMap jobMap){
        String jobName = jobMap.get("jobName").toString();//任務名稱
        String jobGroupName = jobMap.get("jobGroupName").toString();//任務組名
        String lockJobName = "LOCK_" + jobName + "_" +jobGroupName;//redis鎖定job名稱
        /* 通過lockJobName更新數據庫中相應記錄狀態 */
    }

5.定時任務接口類IJob
實現定時任務的具體邏輯,就是quartz要求的那個。

public interface IJob {
    public void execute(JobExecutionContext context) throws JobExecutionException;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章