天天看點

Spring Boot 2.X(十二):定時任務| 用來指定具體的周數,"#"前面代表星期,"#"後面代表本月的第幾周。如"2#1"表示本月第二周的星期日

簡介

定時任務是後端開發中常見的需求,主要應用場景有定期資料報表、定時消息通知、異步的背景業務邏輯處理、日志分析處理、垃圾資料清理、定時更新緩存等等。

Spring Boot 內建了一整套的定時任務工具,讓我們專注于完成邏輯,剩下的基礎排程工作将自動完成。

通用實作方式

實作方式 描述
java.util.Timer Timer 提供了一個 java.util.TimerTask 任務支援任務排程。該方式隻能按指定頻率執行,不能在指定時間運作。由于功能過于單一,使用較少。
Quartz Quartz 是一個功能比較強大的排程器,支援在指定時間運作,也可以按照指定頻率執行。缺點是使用起來相對麻煩。
Spring 架構自帶的 Schedule 子產品 可以看做是輕量級的 Quartz

靜态定時任務

@Component
@EnableScheduling
public class StaticScheduleJob {
    
    /**
     * 上次開始執行時間點後5秒再次執行
     */
    @Scheduled(fixedRate = 5000)
    public void job3() {
        System.out.println("執行任務job3:"+DateUtil.formatDateTime(new Date()));
    }
    
    /**
     * 上次執行完畢時間點後3秒再次執行
     */
    @Scheduled(fixedDelay = 3000)
    public void job2() {
        System.out.println("執行任務job2:"+DateUtil.formatDateTime(new Date()));
    }
    
    /**
     * 每隔10秒執行一次(按照 corn 表達式規則執行)
     */
    @Scheduled(cron = "0/10 * * * * ?")
    public void job1() {
        System.out.println("執行任務job1:"+DateUtil.formatDateTime(new Date()));
    }

    
}
           

@EnableScheduling 注解啟用定時調動功能

@Scheduled 參數說明:

  • @Scheduled(fixedRate = 5000):上次開始執行時間點後5秒再次執行
  • @Scheduled(fixedDelay = 3000):上次執行完畢時間點後3秒再次執行
  • @Scheduled(cron = "0/10 ?"):每隔10秒執行一次(按照 corn 表達式規則執行)

Cron 表達式

1.Cron表達式格式

{秒} {分} {時} {日} {月} {周} {年(可選)}

2.Cron 表達式字段取值範圍及說明

字段 取值範圍 允許的特殊字元
Seconds(秒) 0 ~ 59 , - * /
Minutes(分)
Hours(時) 0 ~ 23
Day-of-Month(天) 可以用數字 1 ~ 31 中的任意一個值,但要注意特殊月份 , - * ? / L W C
Month(月) 可以用 0 ~ 11 或者字元串 “JAN、FEB、MAR、APR、MAY、JUN、JUL、AUG、SEP、OCT、NOV、DEC” 表示
Day-of-Week(每周) 可以用數字 1 ~ 7 表示(1=星期日)或者用字元串 “SUN、MON、TUE、WED、THU、FRI、SAT” 表示 , - * ? / L C #
Year(年) 取值範圍(1970-2099),允許為空值

3.Cron 表達式中特殊字元的意義

  • | 表示可以比對該域的所有值

    ?| 主要用于日和星期,可以比對域的任意值,但實際不會。當2個子表達式其中之一被指定了值以後,為了避免沖突,需要将另一個子表達式的值設為?

/ | 表示為每隔一段時間。如 0 0/10 * ? 其中的 0/10表示從0分鐘開始,每隔10分鐘執行一次

  • | 表示範圍。如 0 0-5 14 ? 表示在每天14:00到14:05期間每1分鐘執行一次

    , | 表示枚舉多個值,這些值之間是"或"的關系。如 0 10,30 14 * 3 ? 表示每個星期二14點10分或者14點30分執行一次

L | 表示每月或者每周的最後一天。如 0 0 0 L ? 表示每月的最後一天執行

W | 表示最近工作日。如 0 0 0 15W ? 表示每月15号最近的那個工作日執行

| 用來指定具體的周數,"#"前面代表星期,"#"後面代表本月的第幾周。如"2#1"表示本月第二周的星期日

4.Cron 線上生成工具

http://www.bejson.com/othertools/cron/

動态定時任務

1.實作 SchedulingConfigurer 接口

@Configuration
public class CustomScheduleConfig implements SchedulingConfigurer {

    @Autowired
    private CronTriggerDao cronTriggerDao;

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        // 上次開始執行時間點後5秒再執行
        taskRegistrar.addFixedRateTask(() -> System.out.println("CustomScheduleConfig執行任務job1=>"
                + DateUtil.formatDateTime(new Date()) + ",線程:" + Thread.currentThread().getName()), 5000);
        // 上次執行完畢時間點後3秒再執行
        taskRegistrar.addFixedDelayTask(() -> System.out.println("CustomScheduleConfig執行任務job2=>"
                + DateUtil.formatDateTime(new Date()) + ",線程:" + Thread.currentThread().getName()), 3000);
        // 添加一個配合資料庫動态執行的定時任務
        TriggerTask triggerTask = new TriggerTask(
                // 任務内容.拉姆達表達式
                () -> {
                    // 1)添加任務 Runnable
                    System.out.println("CustomScheduleConfig執行動态任務job3=>" + DateUtil.formatDateTime(new Date()) + ",線程:"
                            + Thread.currentThread().getName());
                    // 2)設定執行周期
                }, triggerContext -> {
                    // 3)從資料庫擷取執行周期
                    String cron = cronTriggerDao.getCronTriggerById(1L);
                    if (cron != null) {
                        // 4)傳回執行周期(Date)
                        return new CronTrigger(cron).nextExecutionTime(triggerContext);
                    } else {
                        return null;
                    }

                });
        taskRegistrar.addTriggerTask(triggerTask);

    }

}           

2.資料庫中初始化資料

這邊為了測試簡單初始化兩行資料:

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for t_cron_trigger
-- ----------------------------
DROP TABLE IF EXISTS `t_cron_trigger`;
CREATE TABLE `t_cron_trigger` (
  `id` int(8) NOT NULL AUTO_INCREMENT COMMENT '任務id',
  `cron` varchar(20) COLLATE utf8_bin DEFAULT NULL COMMENT 'cron表達式',
  `create_time` datetime DEFAULT NULL COMMENT '建立時間',
  `update_time` datetime DEFAULT NULL COMMENT '更新時間',
  `is_deleted` int(1) DEFAULT '0' COMMENT '廢棄狀态 0-否 1-是',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

-- ----------------------------
-- Records of t_cron_trigger
-- ----------------------------
BEGIN;
INSERT INTO `t_cron_trigger` VALUES (1, '0/10 * * * * ?', '2019-10-30 13:40:14', NULL, 0);
INSERT INTO `t_cron_trigger` VALUES (2, '0/7 * * * * ?', '2019-10-30 13:40:34', NULL, 0);
COMMIT;

SET FOREIGN_KEY_CHECKS = 1;
           

3.從資料庫中讀取 cron 表達式值

CronTrigger 實體類

public class CronTrigger implements Serializable{

    /**
     * 
     */
    private static final long serialVersionUID = 880141459783509786L;
    
    private Long id;//任務id
    private String cron;//cron表達式
    private String createTime;//建立時間
    private String updateTime;//更新時間
    private boolean isDeleted=false;//删除狀态
    
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getCron() {
        return cron;
    }
    public void setCron(String cron) {
        this.cron = cron;
    }
    public String getCreateTime() {
        return createTime;
    }
    public void setCreateTime(String createTime) {
        this.createTime = createTime;
    }
    public String getUpdateTime() {
        return updateTime;
    }
    public void setUpdateTime(String updateTime) {
        this.updateTime = updateTime;
    }
    public boolean isDeleted() {
        return isDeleted;
    }
    public void setDeleted(boolean isDeleted) {
        this.isDeleted = isDeleted;
    }
}           

CronTriggerDao

public interface CronTriggerDao {
    /**
     * 根據id擷取cron表達式
     * @param id
     * @return
     */
    String getCronTriggerById(Long id);

}
           

CronTriggerMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.4//EN" 
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.zwqh.springboot.dao.CronTriggerDao">
    <resultMap type="cn.zwqh.springboot.model.CronTrigger" id="cronTrigger">
        <id property="id" column="id"/>
        <result property="cron" column="cron"/>
        <result property="createTime" column="create_time"/>
        <result property="updateTime" column="update_time"/>
        <result property="isDeleted" column="is_deleted"/>
    </resultMap>
    <!-- 根據id擷取cron表達式 -->
    <select id="getCronTriggerById" resultType="String">
        select cron from t_cron_trigger where is_deleted=0 and id=#{id}
    </select>
</mapper>

           

4.測試

啟動前記得在啟動類上加上 @EnableScheduling

列印日志如下:

Spring Boot 2.X(十二):定時任務| 用來指定具體的周數,"#"前面代表星期,"#"後面代表本月的第幾周。如"2#1"表示本月第二周的星期日

多線程定時任務

通過上面的日志我們可以看到任務執行都是單線程的。如果要實作多線程執行任務,我們可以通過在 SchedulingConfigurer 接口的 configureTasks方法中添加線程池即可。

1.CustomScheduleConfig

@Configuration
public class CustomScheduleConfig implements SchedulingConfigurer {

    @Autowired
    private CronTriggerDao cronTriggerDao;

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        
        //設定一個長度為10的定時任務線程池
        taskRegistrar.setScheduler(Executors.newScheduledThreadPool(10));
        
        // 上次開始執行時間點後5秒再執行
        taskRegistrar.addFixedRateTask(() -> System.out.println("CustomScheduleConfig執行任務job1=>"
                + DateUtil.formatDateTime(new Date()) + ",線程:" + Thread.currentThread().getName()), 5000);
        // 上次執行完畢時間點後3秒再執行
        taskRegistrar.addFixedDelayTask(() -> System.out.println("CustomScheduleConfig執行任務job2=>"
                + DateUtil.formatDateTime(new Date()) + ",線程:" + Thread.currentThread().getName()), 3000);
        // 添加第一個配合資料庫動态執行的定時任務
        TriggerTask triggerTask = new TriggerTask(
                // 任務内容.拉姆達表達式
                () -> {
                    // 1)添加任務 Runnable
                    System.out.println("CustomScheduleConfig執行動态任務job3=>" + DateUtil.formatDateTime(new Date()) + ",線程:"
                            + Thread.currentThread().getName());
                    // 2)設定執行周期
                }, triggerContext -> {
                    // 3)從資料庫擷取執行周期
                    String cron = cronTriggerDao.getCronTriggerById(1L);
                    if (cron != null) {
                        // 4)傳回執行周期(Date)
                        return new CronTrigger(cron).nextExecutionTime(triggerContext);
                    } else {
                        return null;
                    }

                });
        taskRegistrar.addTriggerTask(triggerTask);
        // 添加第二個配合資料庫動态執行的定時任務
        TriggerTask triggerTask2 = new TriggerTask(
                // 任務内容.拉姆達表達式
                () -> {
                    // 1)添加任務 Runnable
                    System.out.println("CustomScheduleConfig執行動态任務job4=>" + DateUtil.formatDateTime(new Date()) + ",線程:"
                            + Thread.currentThread().getName());
                    // 2)設定執行周期
                }, triggerContext -> {
                    // 3)從資料庫擷取執行周期
                    String cron = cronTriggerDao.getCronTriggerById(2L);
                    if (cron != null) {
                        // 4)傳回執行周期(Date)
                        return new CronTrigger(cron).nextExecutionTime(triggerContext);
                    } else {
                        return null;
                    }

                });
        taskRegistrar.addTriggerTask(triggerTask2);

    }

}           

2.測試

Spring Boot 2.X(十二):定時任務| 用來指定具體的周數,"#"前面代表星期,"#"後面代表本月的第幾周。如"2#1"表示本月第二周的星期日

示例代碼

github 碼雲

非特殊說明,本文版權歸

朝霧輕寒

所有,轉載請注明出處.

原文标題:Spring Boot 2.X(十二):定時任務

原文位址:

https://www.zwqh.top/article/info/21

如果文章對您有幫助,請掃碼關注下我的公衆号,文章持續更新中...

Spring Boot 2.X(十二):定時任務| 用來指定具體的周數,"#"前面代表星期,"#"後面代表本月的第幾周。如"2#1"表示本月第二周的星期日