Spring Boot 定時任務
1. 前言
定時任務絕對是實際項目中的剛需。
- 我們想監控一個重點服務的運行狀態,可以每隔 1 分鐘調用下該服務的心跳接口,調用失敗時即發出告警信息;
- 我們想每天凌晨的時候,將所有商品的庫存置滿,以免早上忘記添加庫存影響銷售;
- 我們想在每個周六的某個時段進行打折促銷。
在以上的案例中,或者是指定時間間隔,或者是指定時間節點,按設定的任務進行某種操作,這就是定時任務了。
在 Spring Boot 中實現定時任務簡單而靈活,本節我們來體驗下。
2. Spring Task 定時任務
Spring Task 是 Spring Boot 內置的定時任務模塊,可以滿足大部分的定時任務場景需求。
通過為方法添加一個簡單的注解,即可按設定的規則定時執行該方法。
下面就演示下 Spring Boot 中使用 Spring Task 的具體方法。
2.1 使用 Spring Initializr 創建項目
Spring Boot 版本選擇 2.2.5 ,Group 為 com.imooc , Artifact 為 spring-boot-task ,生成項目后導入 Eclipse 開發環境。
2.2 開啟定時任務
在啟動類上添加 @EnableScheduling
注解,開啟定時任務功能。
實例:
@SpringBootApplication
@EnableScheduling // 開啟定時任務
public class SpringBootTaskApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootTaskApplication.class, args);
}
}
2.3 通過注解設定定時任務
新建 MySpringTask 任務類,添加 @Component
注解注冊 Spring 組件,定時任務方法需要在 Spring 組件類才能生效。
注意類中方法添加了 @Scheduled
注解,所以會按照 @Scheduled
注解參數指定的規則定時執行。
實例:
/**
* 任務類
*/
@Component
public class MySpringTask {
/**
* 每2秒執行1次
*/
@Scheduled(fixedRate = 2000)
public void fixedRateMethod() throws InterruptedException {
System.out.println("fixedRateMethod:" + new Date());
Thread.sleep(1000);
}
}
上面例子執行情況如下,可見是每隔 2 秒執行 1 次。
fixedRateMethod:Fri May 15 22:04:52 CST 2020
fixedRateMethod:Fri May 15 22:04:54 CST 2020
fixedRateMethod:Fri May 15 22:04:56 CST 2020
實例:
/**
* 任務類
*/
@Component
public class MySpringTask {
/**
* 執行結束2秒后執行下次任務
*/
@Scheduled(fixedDelay = 2000)
public void fixedDelayMethod() throws InterruptedException {
System.out.println("fixedDelayMethod:" + new Date());
Thread.sleep(1000);
}
}
上面的例子執行情況如下,每次打印后先等待 1 秒,然后方法執行結束 2 秒后再次執行任務,所以是每 3 秒打印 1 行內容。
fixedDelayMethod:Fri May 15 22:08:26 CST 2020
fixedDelayMethod:Fri May 15 22:08:29 CST 2020
fixedDelayMethod:Fri May 15 22:08:32 CST 2020
2.4 使用 Cron 表達式
@Scheduled
也支持使用 Cron 表達式, Cron 表達式可以非常靈活地設置定時任務的執行時間。以本節開頭的兩個需求為例:
- 我們想監控一個重點服務的運行狀態,可以每隔 1 分鐘調用下該服務的心跳接口,調用失敗時即發出告警信息;
- 我們想在每天凌晨的時候,將所有商品的庫存置滿,以免早上忘記添加庫存影響銷售。
對應的定時任務實現如下:
實例:
/**
* 任務類
*/
@Component
public class MySpringTask {
/**
* 在每分鐘的00秒執行
*/
@Scheduled(cron = "0 * * * * ?")
public void jump() throws InterruptedException {
System.out.println("心跳檢測:" + new Date());
}
/**
* 在每天的00:00:00執行
*/
@Scheduled(cron = "0 0 0 * * ?")
public void stock() throws InterruptedException {
System.out.println("置滿庫存:" + new Date());
}
}
Cron 表達式并不難理解,從左到右一共 6 個位置,分別代表秒、時、分、日、月、星期,以秒為例:
- 如果該位置上是
0
,表示在第 0 秒執行; - 如果該位置上是
*
,表示每秒都會執行; - 如果該位置上是
?
,表示該位置的取值不影響定時任務,由于月份中的日和星期可能會發生意義沖突,所以日、 星期中需要有一個配置為?
。
按照上面的理解,cron = "0 * * * * ?"
表示在每分鐘的 00 秒執行、cron = "0 0 0 * * ?"
表示在每天的 00:00:00 執行。
Tips:Cron 表達式的描述能力很強,此處只是簡單提及,感興趣的同學可以自行查閱相關資料了解更多信息。
3. Quartz 定時任務
Spring Task 已經可以滿足絕大多數項目對定時任務的需求,但是在企業級應用這個領域,還有更加強大靈活的 Quartz 框架可供選擇。
舉個例子,當我們想根據數據庫中的配置,動態地指定商品打折的時間區間時,就可以利用 Quartz 框架來實現。 OK ,接下來我們就來具體完整實現下。
3.1 使用 Spring Initializr 創建項目
Spring Boot 版本選擇 2.2.5 ,Group 為 com.imooc , Artifact 為 spring-boot-quartz ,生成項目后導入 Eclipse 開發環境。
3.2 引入項目依賴
需要引入 Quartz 框架相關依賴。
實例:
<!-- Quartz -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
3.3 開啟定時任務
同樣需要,在啟動類上添加 @EnableScheduling
注解,開啟定時任務功能。
實例:
@SpringBootApplication
@EnableScheduling // 開啟定時任務
public class SpringBootQuartzApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootQuartzApplication.class, args);
}
}
3.4 Quartz 定時任務開發
Quartz 定時任務需要通過 Job 、 Trigger 、 JobDetail 來設置。
- Job:具體任務操作類
- Trigger:觸發器,設定執行任務的時間
- JobDetail:指定觸發器執行的具體任務類及方法
我們先開發一個 Job 組件:
實例:
/**
* 打折任務
*/
@Component // 注冊到容器中
public class DiscountJob {
/**
* 執行打折
*/
public void execute() {
System.out.println("更新數據庫中商品價格,統一打5折");
}
}
然后在配置類中設定 Trigger 及 JobDetail 。
實例:
/**
* 定時任務配置
*/
@Configuration
public class QuartzConfig {
/**
* 配置JobDetail工廠組件,生成的JobDetail指向discountJob的execute()方法
*/
@Bean
MethodInvokingJobDetailFactoryBean jobFactoryBean() {
MethodInvokingJobDetailFactoryBean bean = new MethodInvokingJobDetailFactoryBean();
bean.setTargetBeanName("discountJob");
bean.setTargetMethod("execute");
return bean;
}
/**
* 觸發器工廠
*/
@Bean
CronTriggerFactoryBean cronTrigger() {
CronTriggerFactoryBean bean = new CronTriggerFactoryBean();
// Corn表達式設定執行時間規則
bean.setCronExpression("0 0 8 ? * 7");
// 執行JobDetail
bean.setJobDetail(jobFactoryBean().getObject());
return bean;
}
}
具體分析下上面的代碼:
- 觸發器設定的 Corn 表達式為
0 0 8 ? * 7
,表示每周六的 08:00:00 執行 1 次; - 觸發器指定的 JobDetail 為 jobFactoryBean 工廠的一個對象,而 jobFactoryBean 指定的對象及方法為 discountJob 與 execute () ;
- 所以每周六的 8 點,就會運行 discountJob 組件的 execute () 方法 1 次;
- Corn 表達式和執行任務、方法均以參數形式存在,這就意味著我們完全可以根據文件或數據庫配置動態地調整執行時間和執行的任務;
- 最后,周六 8 點的時候,商品都打了 5 折,別忘了促銷結束的時候恢復價格啊。
4. 小結
Spring Boot 可以利用一個簡單的注解,快速實現定時任務的功能。
說實話我第一次使用 @Scheduled
注解時,完全被這種開箱即用型的簡潔震撼了,我的感受是:似乎不能更加簡潔了。
如果感覺 Spring Task 提供的定時任務機制還不足以滿足需求,Spring Boot 還可以方便地集成 Quartz 框架來幫忙。
開箱即用滿足不了,還可以即插即用,確實夠人性化的。