Spring實現(xiàn)定時任務的兩種方法詳解
1. 概要
時間輪的文章在上幾篇文章中就已經(jīng)介紹完了,那么 Java 中 Spring 的定時任務肯定也是跑不掉的,那首先要學 Spring 里面定時任務的邏輯,就得先學會用法。關于 Spring,其實提供了兩種方式實現(xiàn)定時任務,一種是注解,還有一種就是接口了,下面我就會講一下這兩種方式的用法,在下一篇文章中我們會繼續(xù)深入 Spring 源碼,去講解 Spring 里面的定時任務到底是怎么實現(xiàn)的。
2. 接口方式動態(tài)配置
2.1 抽象類
通過接口的方式可以實現(xiàn)時間的動態(tài)配置,先看下抽象類:
@Slf4j
@Configuration
@EnableScheduling
public abstract class ScheduledConfig implements SchedulingConfigurer {
//定時任務周期表達式
private String cron;
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
//設置線程池,可開啟多線程
taskRegistrar.setScheduler(taskExecutor());
taskRegistrar.addTriggerTask(
// 執(zhí)行定時任務
() -> {
taskService();
},
triggerContext -> {
// 這里就是動態(tài)獲取任務隊列的邏輯
cron = getCron();
if(cron == null){
throw new RuntimeException("cron not exist");
}
// 重新獲取cron表達式
CronTrigger trigger = new CronTrigger(cron);
return trigger.nextExecutionTime(triggerContext);
}
);
}
private Executor taskExecutor() {
return BeanUtils.getBean(ThreadPoolTaskScheduler.class);
}
/**
* @Description: 執(zhí)行定時任務
* @param:
* @return: void
* @Author:
* @Date: 2020/8/28
*/
public abstract void taskService();
/**
* @Description: 獲取定時任務周期表達式
* @param:
* @return: java.lang.String
* @Author:
* @Date: 2020/8/28
*/
public abstract String getCron();
/**
* 判斷某任務是否開啟
* @return
*/
public abstract int getOpen();
@Bean
public ThreadPoolTaskScheduler threadPoolTaskScheduler(){
ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler();
executor.setPoolSize(16);
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
return executor;
}
}
實現(xiàn) SchedulingConfigurer 接口之后重寫 configureTasks 方法,可以設置任務的具體調(diào)度邏輯,也就是 taskRegistrar.addTriggerTask。那么里面的具體邏輯是什么呢?
// 執(zhí)行定時任務
() -> {
taskService();
},
triggerContext -> {
// 這里就是動態(tài)獲取任務隊列的邏輯
cron = getCron();
if(cron == null){
throw new RuntimeException("cron not exist");
}
// 重新獲取cron表達式
CronTrigger trigger = new CronTrigger(cron);
return trigger.nextExecutionTime(triggerContext);
}
定時任務里面執(zhí)行具體的任務,同時重寫觸發(fā)器的 nextExecutionTime 方法,在里面重新獲取 cron 表達式然后更新下一次的執(zhí)行時間。
同時最后設置了執(zhí)行任務的線程池,這樣定時任務執(zhí)行的邏輯就會交給線程池去執(zhí)行,不需要阻塞當前的工作線程。
2.2 具體實現(xiàn)類
@Component
public class ScheduledJob extends ScheduledConfig {
String cron = "0/1 * * * * ?";
long count = 0;
@Override
public void taskService() {
int open = getOpen();
if(open == 1){
count++;
PrintUtils.printLog("執(zhí)行任務!!!, 當前表達式:%s", cron);
}
}
@Override
public String getCron() {
if(count == 5){
cron = "0/5 * * * * ?";
}
return cron;
}
@Override
public int getOpen() {
return 1;
}
}
里面的 getOpen() 就是去獲取定時任務是否開啟的,getCron() 是獲取任務執(zhí)行 cron 表達式,這兩個配置都可以寫在數(shù)據(jù)庫里面去更改。
2.3 工具類
首先是 Beanutils,從 Spring 容器中獲取對應的 bean
@Component
public class BeanUtils implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
synchronized (BeanUtils.class){
BeanUtils.applicationContext = applicationContext;
}
}
public static <T> T getBean(String beanName) {
synchronized (BeanUtils.class){
if(applicationContext.containsBean(beanName)){
return (T) applicationContext.getBean(beanName);
}else{
return null;
}
}
}
public static <T> Map<String, T> getBeansOfType(Class<T> baseType){
synchronized (BeanUtils.class) {
return applicationContext.getBeansOfType(baseType);
}
}
public static <T> T getBean(Class<T> baseType){
synchronized (BeanUtils.class) {
return applicationContext.getBean(baseType);
}
}
}
然后就是打印的工具類 PrintUtils
public class PrintUtils {
static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss:SSS");
public static String now(){
return LocalDateTime.now().format(FORMATTER);
}
public static void printLog(String str, Object...args){
System.out.println(now() + ": " + String.format(str, args));
}
}
2.4 執(zhí)行結果

輸出結果如上所示:可以看到在第五次輸出之后 cron 被改成了 5s 執(zhí)行一次,但是注意第 6 次距離第 5 次是 3s,可能是內(nèi)部的一些其他的處理,后面源碼再看。
3. 注解方式靜態(tài)配置
在 Spring 中,默認情況下,@Scheduled 注解標記的任務是由 Spring 內(nèi)部的一個單線程調(diào)度器(TaskScheduler)來執(zhí)行的。但是如果我們需要用自定義的線程池來執(zhí)行這些任務,可以通過配置自定義的 TaskScheduler 或 ThreadPoolTaskScheduler 來實現(xiàn)。ThreadPoolTaskScheduler 里面是通過 ScheduledExecutorService 來實現(xiàn)的。
@Scheduled 注解可以實現(xiàn)定時任務、固定速率任務、固定延時任務三種,下面就一種一種來看,不過在看任務之前,先來看下一些必要的配置。
1.首先是開啟 @EnableScheduling 注解
@SpringBootApplication
@EnableScheduling
public class ScheduleApplication {
public static void main(String[] args) {
SpringApplication.run(ScheduleApplication.class, args);
}
}
2.然后是配置線程池
@Configuration
public class SchedulerConfig {
@Bean
public ThreadPoolTaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(10); // 設置線程池大小
scheduler.setThreadNamePrefix("MyTaskScheduler-"); // 設置線程名稱前綴
scheduler.initialize(); // 初始化調(diào)度器
return scheduler;
}
}
3.1 定時任務
@Service
public class ScheduledService {
@Scheduled(cron = "0/5 * * * * ?")
public void runEvery15Minutes() {
PrintUtils.printLog( "This task runs every 5 seconds");
}
}
定時任務通過 cron 表達式來實現(xiàn),輸出結果如下:

3.2 固定延時任務
定時/延時任務-ScheduledThreadPoolExecutor的使用,固定速率和固定延時就看上面的區(qū)別就行了,因為 Spring 底層 Debug 了下默認創(chuàng)建出來的就是 ScheduledThreadPoolExecutor。不過其實核心思想都是一樣的,就算自己去實現(xiàn)了,固定速率和固定延時的核心都是一樣的,只是實現(xiàn)上會不一樣,可以對比下 ScheduledThreadPoolExecutor 和 Timer 的實現(xiàn)。Timer 的解析也在往期文章里面。定時/延時任務-Timer用法
代碼如下所示:
@Service
public class ScheduledService {
@Scheduled(fixedDelay = 5000)
public void runEvery15Minutes() throws InterruptedException {
Thread.sleep(6000);
PrintUtils.printLog("This task runs every 5 seconds, sleep 6 seconds");
}
}結果輸出如下,固定延時就是要確保本次任務執(zhí)行的時間距離上一次任務執(zhí)行完成的時間相差 5s

3.3 固定速率任務
@Service
public class ScheduledService {
@Scheduled(fixedRate = 5000)
public void runEvery15Minutes() throws InterruptedException {
Thread.sleep(6000);
PrintUtils.printLog("This task runs every 5 seconds, sleep 6 seconds");
}
}
執(zhí)行結果如下:

固定速率就是確保本次任務執(zhí)行時間距離上次任務執(zhí)行的時間是期望時間 + fixedRate,如果看不懂可以去看上面那兩篇文章,很清晰。
3.4 動態(tài)配置
@Scheduled 注解使用固定的時間間隔或 cron 表達式來定義任務的執(zhí)行頻率,下面就來演示下。
- 在 application.properties 或 application.yml 中定義動態(tài)配置
- 在 @Scheduled 注解中引用這些字段
application.properties 配置文件如下:
# 動態(tài)配置時間間隔(單位:毫秒) custom.fixedRate=5000 # 動態(tài)配置cron表達式 custom.cronExpression=0/5 * * * * ?
@Service
public class ScheduledService {
@Scheduled(fixedRateString = "${custom.fixedRate}")
public void runEvery15Minutes() throws InterruptedException {
Thread.sleep(6000);
PrintUtils.printLog("This task runs every 5 seconds, sleep 6 seconds");
}
}
@Scheduled(cron = "${custom.cronExpression}")
public void runEvery15Minutes() throws InterruptedException {
Thread.sleep(6000);
PrintUtils.printLog("This task runs every 5 seconds, sleep 6 seconds");
}其中一個的輸出結果如下,可以看到都是能正常調(diào)度的。

以上就是Spring實現(xiàn)定時任務的兩種方法詳解的詳細內(nèi)容,更多關于Spring定時任務的資料請關注腳本之家其它相關文章!
相關文章
使用IntelliJ IDEA 進行代碼對比的方法(兩種方法)
這篇文章給大家?guī)砹藘煞NIntelliJ IDEA 進行代碼對比的方法,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2018-01-01
PowerJob的AbstractScriptProcessor實現(xiàn)類工作流程源碼解讀
這篇文章主要為大家介紹了PowerJob的AbstractScriptProcessor源碼流程解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2024-01-01
解決springboot中配置過濾器以及可能出現(xiàn)的問題
這篇文章主要介紹了解決springboot中配置過濾器以及可能出現(xiàn)的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09
Java Kafka實現(xiàn)優(yōu)先級隊列的示例詳解
在分布式系統(tǒng)中,消息隊列是一種常見的異步通信機制,而優(yōu)先級隊列則是消息隊列的一種特殊形式,下面我們來看看如何利用Kafka實現(xiàn)優(yōu)先級隊列吧2025-03-03

