四、springBoot 優雅的創建定時任務

前言

好幾天沒寫了,工作有點忙,最近工作剛好做一個定時任務統計的,所以就將springboot 如何創建定時任務整理了一下。
總的來說,springboot創建定時任務是非常簡單的,不用像spring 或者springmvc 需要在xml 文件中配置,在項目啓動的時候加載。spring boot 使用註解的方式就可以完全支持定時任務。
不過基礎註解的話,可能有的需求定時任務的時間會經常變動,註解就不好修改,每次都得重新編譯,所以想將定時時間存在數據庫,然後項目讀取數據庫執行定時任務,所以就有了基於接口的定時任務。下面就分基於註解和基於接口詳細講解。

基於註解

pom.xml 文件不用修改,我們原本的項目就支持,其實定時器是springboot框架自帶的,不用引入什麼依賴。我們直接創建一個autotask 包,創建一個AutoTask類。

@EnableScheduling
@Component
@Slf4j
public class AutoTask {
    @Scheduled(cron="*/6 * * * * ?")
    private void process(){
        log.info("autoTask ");
    }
}

這樣一個定時器就創建啦,在項目啓動後,會每隔6s 打印“autoTask”的日誌。是不是很簡單。主要用到的兩個註解就是@EnableScheduling 和 @Scheduled。
註解@EnableScheduling 就是開啓定時任務的。哪個類的中的方法想要定期執行,就在這個類上加入這個註解。當然這個這個註解也可以加在啓動類上。加在啓動類上表示項目中所有的類都可以創建定時任務。
file

@Scheduled 註解就是我們常見的定時器啦,後面的cron 就是定時任務表達式。在方法上註解,表示這個方法定期執行。
file
不過@Scheduled 可以進行兩種配置,我們熟悉的cron ,還有一種是fixedRate。比如fixedRate=6000 表示方法每6秒鐘執行一次。
我們來啓動項目看一下,可以看到兩個方法都在定期執行。
file

基於接口

上面可以看到springboot 基於註解是非常方便的。但是對於頻繁變動或者一個項目中有很多的定時器那就不方便管理了。所以統一將定時器信息存放在數據庫中。


DROP TABLE IF EXISTS `scheduled`;
CREATE TABLE `scheduled`  (
  `cron_id` varchar(30) NOT NULL PRIMARY KEY,
  `cron_name` varchar(30) NULL,
  `cron` varchar(30) NOT NULL
);
INSERT INTO `scheduled` VALUES ('1','定時器任務一','0/6 * * * * ?');

file

在dao 層mapper1包下創建一個CronMapper接口,很簡單的就獲取cron

public interface CronMapper {

    @Select("select cron from scheduled where cron_id = #{id}")
    public String getCron(int id);
}

file

這裏我們就不寫service 層了。直接在autotask 包下創建一個AutoTaskFromDB類

@Slf4j
@Component
public class AutoTaskFromDB implements SchedulingConfigurer {

    @Autowired
    protected CronMapper cronMapper;

    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {

        scheduledTaskRegistrar.addTriggerTask(() -> process(),
                triggerContext -> {
                    String cron = cronMapper.getCron(1);
                    if (cron.isEmpty()) {
                       log.info("cron 爲空");
                    }
                    return new CronTrigger(cron).nextExecutionTime(triggerContext);
                }
        );
    }

    private  void process(){
        log.info("formDB ");
    }
}

可以看到也很簡單,就是實現SchedulingConfigurer 這個吧接口,addTriggerTask()是添加一個定時器。
process()方法是我們需要定時執行的方法體。
CronTrigger(cron).nextExecutionTime(triggerContext) 就是從數據庫讀取的cron 創建定時器。

這個類我沒有加上@EnableScheduling 註解,因爲我在啓動類上加上了,如果你們啓動類上沒有加,這裏記得加上。

測試一下;下圖可以看到三個定時任務都執行了,fromDB 是從數據庫讀取的。
file

cron

cron 用法網上有很多,也沒有什麼講的這裏就附帶記錄下

結構

cron表達式是一個字符串,分爲6或7個域,每兩個域之間用空格分隔,
其語法格式爲:“秒域 分域 時域 日域 月域 周域 年域”

取值範圍

|域名| 可取值| 可取符號(僅列部分常用)|
|–|--|
|秒域| 0~59的整數| * - , /
|分域| 0~59的整數| * - , /
|時域 |0~23的整數| * - , /
|日域 |1~31的整數| * - , / ? L
|月域 |112的整數或JANDEC| * - , /
|周域 |17的整數或SUNSAT| * - , / ? L #
|年域 |1970~2099的整數 | * - , /

常例

表達式 意義
每隔5秒鐘執行一次 */5 * * * * ?
每隔1分鐘執行一次 0 * /1 * * * ?
每天1點執行一次 0 0 1 * * ?
每天23點55分執行一次 0 55 23 * * ?
每月最後一天23點執行一次 0 0 23 L * ?
每週六8點執行一次 0 0 8 ? * L
每月最後一個週五,每隔2小時執行一次 0 0 */2 ? * 6L
每月的第三個星期五上午10:15執行一次 0 15 10 ? * 5#3
在每天下午2點到下午2:05期間的每1分鐘執行 0 0-5 14 * * ?
表示週一到週五每天上午10:15執行 0 15 10 ? * 2-6
每個月的最後一個星期五上午10:15執行 0 15 10 ? * 6L
每天上午10點,下午2點,4點執行一次 0 0 10,14,16 * * ?
朝九晚五工作時間內每半小時執行一次 0 0/30 9-17 * * ?
每個星期三中午12點執行一次 0 0 12 ? * 4
每年三月的星期三的下午2:10和2:44各執行一次 0 10,44 14 ? 3 4
每月的第三個星期五上午10:15執行一次 0 15 10 ? * 6#3
每月一日凌晨2點30執行一次 0 30 2 1 * ?
每分鐘的第10秒與第20秒都會執行 10,20 * * * * ?
每月的第2個星期的周5,凌晨執行 0 0 0 ? * 6#2

番外

本來這個知識點不應該放在這裏講的,但是不多,順帶寫了,剛好也能做定時器。我們項目中往往有一些需求需要在項目啓動的時候就執行,那這個我們怎麼實現了。其實spring boot 使用起來也非常簡單,只用實現 ApplicationRunner 就好了。
我們在autotask 包下創建一個AutoTaskFromSpringRunner類

@Slf4j
@Component
public class AutoTaskFromSpringRunner implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        process();
    }

    private void process(){
        log.info(" run ApplicationArguments");
    }
}

啓動項目看一下,可以發現這個會在項目啓動後執行,但是隻會執行一次。

file
那這個怎麼用來做定時器呢?當然是結合線程來做啦,但是這個方法其實不建議,b畢竟線程很容易出問題,但是提供一種思路:

@Slf4j
@Component
public class AutoTaskFromSpringRunner implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        process();
        new Thread(() -> {
            while (true) {
                process2();
                try {
                    Thread.sleep(6000);
                } catch (InterruptedException e) {
                    log.error("{}",e);
                }
            }
        }).start();
    }
    private void process(){
        log.info(" run ApplicationArguments");
    }
    private void process2(){
        log.info("線程定時器");
    }
}

啓動項目看下,發現也是可以起到定時器的作用的。
file

好了,就說這麼多啦,今天項目的代碼也同步到github 上啦。
github地址:https://github.com/QuellanAn/zlflovemm

後續加油♡

歡迎大家關注個人公衆號 “程序員愛酸奶”

分享各種學習資料,包含java,linux,大數據等。資料包含視頻文檔以及源碼,同時分享本人及投遞的優質技術博文。

如果大家喜歡記得關注和分享喲❤
file

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