1. 程式人生 > >spring定時任務實現動態定時任務(啟停,週期修改)

spring定時任務實現動態定時任務(啟停,週期修改)

使用方法:

            複製下方程式碼,在業務需要處呼叫(定時任務狀態和執行週期被修改後),一定要在專案啟動時後立刻執行一次庫中全資料呼叫此方法,List<Cron> crons   Cron中一定要有業務類的包加類名(com.rails.travel.conf.task.myschedule.MyRunnable),有啟停的狀態,有執行週期這是必須的引數,根據自己業務可以修改一個定時任務或多個因為是list

描述:

            在我們專案中很多會遇到需要定時任務的地方,但是我們如果利用spring的註解定時任務需要修改時間或者啟停任務都要修改專案並重新部署和啟動專案,這在很多時候是不能滿足我們的需求的,這篇文章主要介紹雜麼利用spring的定時任務實現對定時任務的動態啟停和執行週期的修改

     之前介紹了

實現思路:

            1、既然要實現動態我們肯定要有我們動態資料,資料當然是儲存到資料庫中最合適,先建立一個表,表裡的欄位主要包含id(表id),classname(當前定時任務類名,很重要),cron(執行週期表達式),classparma(執行定時任務的引數),status(是否開啟當前定時任務),最好還要有一個對此任務的描述欄位,便於維護。表結構如下

            2、表有了資料有了,我們肯定不能總是操作表,我們需要一個管理頁面來做這些事,所以我們根據自己的業務需求做一個對該表的增刪改查頁面。頁面如下

           3、動態資料和操作頁面都有了,接下來就是我們雜麼讓spring的定時任務動態獲取到資料而且實時生效。網上有對定時任務單純動態時間週期的,有單純對定時任務實現啟停的,但是這2中方法都是獨立的,修改時間是一個物件,啟停又是一個物件,這樣就會有一個定時任務多次跑的問題,結合2種方法整合一套可以操作到一個定時任務一個物件。

                具體思路是這樣的動態獲取庫中所有定時任務List<Cron> crons,呼叫startCron方法,利用scheduledFuture.cancel(true);把list中的所有定時任務都先停止,接著判斷list中的定時任務是否有效,有效則利用ScheduledFuture<?> future = threadPoolTaskScheduler.schedule((Runnable) Class.forName(cron.getCronClass()).newInstance(), new CronTrigger(cron.getCron()));開啟一個新的定時任務,無效則不做操作,此時已經實現了定時任務的啟停和動態週期,接著把啟動的定時任務物件儲存到map中,key為庫中的類名,value為ScheduledFuture物件,方便下次根據key獲取到物件進行定時任務的停止。

程式碼如下


import com.rails.extrs.utils.ToolUtil;
import com.rails.travel.core.constant.COMMON_API_WRAPPER_STATIC_VALUE;
import com.rails.travel.core.domain.Cron;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;

import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ScheduledFuture;


@Configuration
public class ScheduleConfig {

    private static final Logger logger = LoggerFactory.getLogger(ScheduleConfig.class);

    @Autowired
    private ThreadPoolTaskScheduler threadPoolTaskScheduler;


    /**
     * 存放所有啟動定時任務物件,極其重要
     */
    private HashMap<String, ScheduledFuture<?>> scheduleMap = new HashMap<>();

    @Bean
    public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
        return new ThreadPoolTaskScheduler();
    }


    /**
     * @param crons
     * 動態設定定時任務方法
     * 
     * 此方法是真正的動態實現啟停和時間週期的關鍵,你可以針對自己的業務來呼叫,你對庫中的動態資料修改後來呼叫此方法,每個Cron物件必須要包含,執行週期(cron.getCron()),啟停狀態(cron.getCronStatus()),執行的類(cron.getCronClass())
     */
    public void startCron(List<Cron> crons){
        try {
            //遍歷所有庫中動態資料,根據庫中class取出所屬的定時任務物件進行關閉,每次都會把之前所有的定時任務都關閉,根據新的狀態重新啟用一次,達到最新配置
            for (Cron cron : crons){
                ScheduledFuture<?> scheduledFuture = scheduleMap.get(cron.getCronClass());
                //一定判空否則出現空指標異常,ToolUtil為自己寫的工具類此處只需要判斷物件是否為空即可
                if (ToolUtil.isNotEmpty(scheduledFuture)){
                    scheduledFuture.cancel(true);
                }
            }
            //因為下邊儲存的是新的定時任務物件,以前的定時任務物件已經都停用了,所以舊的資料沒用清除掉,這步可以不處理,因為可以是不可重複要被覆蓋
            //scheduleMap.clear();
            //遍歷庫中資料,之前已經把之前所有的定時任務都停用了,現在判斷庫中如果是啟用的重新啟用並讀取新的資料,把開啟的資料物件儲存到定時任務物件中以便下次停用
            for (Cron cron : crons){
                //判斷當前定時任務是否有效,COMMON_API_WRAPPER_STATIC_VALUE.VALIDFLAG.TRUE為有效標識
                if (cron.getCronStatus().equals(COMMON_API_WRAPPER_STATIC_VALUE.VALIDFLAG.TRUE)) {
                    //開啟一個新的任務,庫中儲存的是全類名(包名加類名)通過反射成java類,讀取新的時間
                    ScheduledFuture<?> future = threadPoolTaskScheduler.schedule((Runnable) Class.forName(cron.getCronClass()).newInstance(), new CronTrigger(cron.getCron()));
                    //這一步非常重要,之前直接停用,只停用掉了最後啟動的定時任務,前邊啟用的都沒辦法停止,所以把每次的物件存到map中可以根據key停用自己想要停用的
                    scheduleMap.put(cron.getCronClass(),future);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

            接著(Runnable) Class.forName(cron.getCronClass()).newInstance()這是一個解析字串為類名的一個反射方法,我們需要有這個真正執行我們業務程式碼的類


import java.util.Date;

public class MyRunnable implements Runnable {

    @Override
    public void run() {
        System.out.print("業務執行了" + new Date());
    }
}

             現在動態啟停修改已經實現了,還有一個問題就是現在在什麼時候呼叫此方法,當然是在資料庫資料被修改和狀態被修改後呼叫此方法即可,但是我們專案啟動就要執行定時任務,所以要有一個專案啟動成功後把表中資料查詢出來呼叫此方法的一個步驟(專案載入完執行一次呼叫資料庫所有資料並把資料作為引數傳遞到方法即可)