零、前言
spring boot進行定時任務一共有三種方式,第一種也就是最簡單的一種:基於註解 (@Scheduled)的方式;第二種:基於接口 (SchedulingConfigurer);第三種:基於註解設定多線程定時任務。
一、基於註解的方式
首先,打開idea,創建springboot項目,無需引入任何jar,springboot自帶定時。
然後,在啓動類中用註解@EnableScheduling進行標註,表明此類 存在定時任務。在定時執行的方法之上添加註解
@Scheduled(cron ="*/6 * * * * ?")。
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
@SpringBootApplication
@EnableScheduling
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Scheduled(cron ="*/6 * * * * ?")
public void sayHello() {
System.out.println("hello");
}
}
點擊啓動,即可看到控制檯6秒輸出一次“hello”。
當然,定時任務也可以放在其他類中。例如創建類Task1。
package com.example.task;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
/**
* @Description
* @ClassName Task1
* @Author User
* @date 2020.06.07 12:24
*/
@Component
public class Task1 {
@Scheduled(cron ="*/1 * * * * ?")
public void sayWord() {
System.out.println("world");
}
}
然後可以看到控制檯的輸出結果:
這裏有個要注意的細節,就是啓動類需要能掃描到定時任務類,否則定時任務啓動不起來。不僅需要@Component註解,也需要將啓動類位置位於定時任務類之上。如下圖:
筆者就是犯了這樣的錯,一直沒啓動起來。
@Scheduled除過cron還有三種方式:fixedRate,fixedDelay,initialDelay
cron:表達式可以定製化執行任務,但是執行的方式是與fixedDelay相近的,也是會按照上一次方法結束時間開始算起。
fixedRate:控制方法執行的間隔時間,是以上一次方法執行完開始算起,如上一次方法執行阻塞住了,那麼直到上一次執行完,並間隔給定的時間後,執行下一次。
@Configuration
@EnableScheduling //開啓定時任務
public class ScheduleTask1 {
//每3秒執行一次
@Scheduled(fixedDelay = 3000)
private void myTasks() {
System.out.println("I do myself per third seconds");
}
}
fixedRate:是按照一定的速率執行,是從上一次方法執行開始的時間算起,如果上一次方法阻塞住了,下一次也是不會執行,但是在阻塞這段時間內累計應該執行的次數,當不再阻塞時,一下子把這些全部執行掉,而後再按照固定速率繼續執行。
@Component
@EnableScheduling //開啓定時任務
public class ScheduleTask2 {
//每10秒執行一次
@Scheduled(fixedRate = 10000)
private void myTasks2() {
System.out.println("我是一個定時任務");
}
}
initialDelay:initialDelay = 10000 表示在容器啓動後,延遲10秒後再執行一次定時器。
@Component
@EnableScheduling //開啓定時任務
public class ScheduleTask {
//容器啓動後,延遲10秒後再執行一次定時器,以後每10秒再執行一次該定時器。
@Scheduled(initialDelay = 10000, fixedRate = 10000)
private void myTasks3() {
System.out.println("我是一個定時任務3");
}
}
二、cron解釋
cron
cron 用法跟linux下是一摸一樣的,如果你搞過linux下的定時,那麼必然很熟悉。
結構
cron表達式是一個字符串,分爲6或7個域,每兩個域之間用空格分隔,
其語法格式爲:"秒域 分域 時域 日域 月域 周域 年域"
取值範圍
域名 | 可取值 | 可取符號(僅列部分常用) |
---|---|---|
秒域 | 0~59的整數 | * - , / |
分域 | 0~59的整數 | * - , / |
時域 | 0~23的整數 | * - , / |
日域 | 1~31的整數 | * - , / ? L |
月域 | 1~12的整數或JAN~DEC | * - , / |
周域 | 1~7的整數或SUN~SAT | * - , / ? 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 |
本方法的demo地址: https://github.com/SUST-MaZhen/scheduledTask.git
三、基於接口的方式
使用@Scheduled 註解很方便,但缺點是當我們調整了執行週期的時候,需要重啓應用才能生效,這多少有些不方便。爲了達到實時生效的效果,那麼可以使用接口來完成定時任務,統一將定時器信息存放在數據庫中。
在mysql中執行一下腳本插入定時任務:
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 * * * * ?');
創建一個springboot 項目:我們這裏只添加一個mapper,不要bean也不要service以及controller,只是爲了演示定時功能而已。demo結構:
數據源基本配置:application.properties
## mysql數據源配置
spring.datasource.url=jdbc:mysql://host:3306/dbname?useUnicode=true&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
## Mybatis 配置
# 配置爲 com.example.bean 指向實體類包路徑
#mybatis.typeAliasesPackage=com.zhenma.bean
mapper也就是dao:
package com.zhenma.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;
@Repository
@Mapper
public interface CronMapper {
@Select("select cron from scheduled where cron_id = #{id}")
public String getCron(int id);
}
task類:
package com.zhenma.scheduled;
import com.zhenma.mapper.CronMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Component;
/**
* @Description
* @ClassName MyTask
* @Author User
* @date 2020.06.07 15:23
*/
@Component
@EnableScheduling
public class MyTask implements SchedulingConfigurer {
@Autowired
protected CronMapper cronMapper;
@Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
scheduledTaskRegistrar.addTriggerTask(() -> process(),
triggerContext -> {
String cron = cronMapper.getCron(1);
if (cron.isEmpty()) {
System.out.println("cron is null");
}
return new CronTrigger(cron).nextExecutionTime(triggerContext);
});
}
private void process() {
System.out.println("基於接口定時任務");
}
}
運行結果:
從結果中可以看出,是按照每6秒也就是數據庫中查詢的結果來進行的。
需求:我現在需要每10秒執行一次定時任務,該怎麼辦呢?對!只需要修改數據庫值即可,server無需重啓。觀察修改後的結果。
感覺好(。・∀・)ノ゙嗨哦。
demo地址:https://github.com/SUST-MaZhen/scheduledtask2.git
四、 基於註解設定多線程定時任務
前面講到了@Scheduled執行週期任務會受到上次一個任務的執行時間影響。那麼可以開啓多線程執行週期任務。
創建springboot項目,創建一個多線程定時任務類如下:
package com.example.task;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
/**
* @Description
* @ClassName MultiThreadTask
* @Author User
* @date 2020.06.07 18:56
*/
@EnableScheduling // 1.開啓定時任務
@EnableAsync // 2.開啓多線程
@Component
public class MultiThreadTask {
@Async
@Scheduled(fixedDelay = 1000) //間隔1秒
public void first() throws InterruptedException {
System.out.println("第一個定時任務開始 : " + LocalDateTime.now().toLocalTime() + "\r\n線程 : " + Thread.currentThread().getName());
Thread.sleep(1000 * 10);
}
@Async
@Scheduled(fixedDelay = 2000)
public void second() {
System.out.println("第二個定時任務開始 : " + LocalDateTime.now().toLocalTime() + "\r\n線程 : " + Thread.currentThread().getName());
}
}
執行結果如下:
從結果可以看出:第一個任務的執行時間也不受其本身執行時間的限制。兩個任務也互不影響。
demo地址: https://github.com/SUST-MaZhen/scheduledtash3.git
五、總結
本文介紹了spring boot創建定時任務的三種方式,當然還有其他方式,例如最近本的定時器來進行等等。記錄爲了使用,多使用便會記憶,便會理解。 寫個博客真費勁,卡在demo上...。