首頁 > 軟體

SpringBoot動態定時功能實現方案詳解

2022-11-17 14:01:10

業務場景

基於上篇程式,做了一版動態定時程式,然後發現這個定時程式需要在下次執行的時候會載入新的時間,所以如果改了定時程式不能馬上觸發,所以想到一種方法,在儲存定時程式的時候將cron表示式傳過去,然後觸發定時程式,下面看看怎麼實現

環境準備

開發環境

  • JDK 1.8
  • SpringBoot2.2.1
  • Maven 3.2+

開發工具

  • IntelliJ IDEA
  • smartGit
  • Navicat15

實現方案

基於上一版進行改進:

  • 先根據選擇的星期生成cron表示式,儲存到資料庫裡,同時更改cron表示式手動觸發定時程式載入最新的cron表示式
  • 根據儲存的cron表示式規則執行定時程式
  • 通過CommandLineRunner設定啟動載入
  • 加上執行緒池,提高執行緒複用率和程式效能

加上ThreadPoolTaskScheduler,支援同步和非同步兩種方式:

import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
@Slf4j
public class ScheduleConfig implements SchedulingConfigurer , AsyncConfigurer {
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setScheduler(taskScheduler());
    }
    @Bean(destroyMethod="shutdown" , name = "taskScheduler")
    public ThreadPoolTaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(10);
        scheduler.setThreadNamePrefix("itemTask-");
        scheduler.setAwaitTerminationSeconds(600);
        scheduler.setWaitForTasksToCompleteOnShutdown(true);
        return scheduler;
    }
    @Bean(name = "asyncExecutor")
    public ThreadPoolTaskExecutor asyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setQueueCapacity(1000);
        executor.setKeepAliveSeconds(600);
        executor.setMaxPoolSize(20);
        executor.setThreadNamePrefix("itemAsyncTask-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
    @Override
    public Executor getAsyncExecutor() {
        return asyncExecutor();
    }
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (throwable, method, objects) -> {
            log.error("非同步任務異常,message {} , method {} , params" , throwable , method , objects);
        };
    }
}

加上一個SchedulerTaskJob介面:

public interface SchedulerTaskJob{
    void executeTask();
}

AbstractScheduler 抽象類,提供基本的功能

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Date;
@Slf4j
@Component
@Data
public abstract class AbstractScheduler implements SchedulerTaskJob{
    @Resource(name = "taskScheduler")
    private ThreadPoolTaskScheduler threadPoolTaskScheduler;
    @Override
    public void executeTask() {
        String cron = getCronString();
        Runnable task = () -> {
            // 執行業務
            doBusiness();
        };
        Trigger trigger = new Trigger()   {
            @Override
            public Date nextExecutionTime(TriggerContext triggerContext) {
                CronTrigger trigger;
                try {
                    trigger = new CronTrigger(cron);
                    return trigger.nextExecutionTime(triggerContext);
                } catch (Exception e) {
                    log.error("cron表示式異常,已經啟用預設設定");
                    // 設定cron表示式異常,執行預設的表示式
                    trigger = new CronTrigger(getDefaultCron());
                    return trigger.nextExecutionTime(triggerContext);
                }
            }
        };
        threadPoolTaskScheduler.schedule(task , trigger);
    }
    protected abstract String getCronString();
    protected abstract void doBusiness();
    protected abstract String getDefaultCron();
}

實現類,基於自己的業務實現,然後事項抽象類,通過模板模式進行程式設計

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.time.LocalDateTime;
@Service
@Slf4j
@Data
public class ItemSyncScheduler extends AbstractScheduler {
    @Value("${configtask.default.itemsync}")
    private String defaultCron ;
    private String cronString ;
    @Override
    protected String getCronString() {
        if (StrUtil.isNotBlank(cronString))  return cronString;
        SyncConfigModel configModel = syncConfigService.getOne(Wrappers.<SyncConfigModel>lambdaQuery()
                .eq(SyncConfigModel::getBizType, 1)
                .last("limit 1"));
        if (configModel == null) return defaultCron;
        return configModel.getCronStr();
    }
    @Override
    protected void doBusiness() {
        log.info("執行業務...");
        log.info("執行時間:{}"  , LocalDateTime.now());
       // 執行業務
    }
    @Override
    protected String getDefaultCron() {
        return defaultCron;
    }
}

如果更改了cron表示式,程式不會馬上觸發,所以直接開放一個介面出來,呼叫的時候,設定最新的表示式,然後重新呼叫定時程式

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
public class ItemSchedulerController {
    private ItemSyncScheduler itemSyncScheduler;
    @Autowired
    public ItemSchedulerController(ItemSyncScheduler itemSyncScheduler) {
        this.itemSyncScheduler= itemSyncScheduler;
    }
    @GetMapping(value = "/updateItemCron")
    @ApiOperation(value = "更新cron表示式")
    public void updateItemCron(@RequestParam("cronString") String cronString) {
        log.info("更新cron表示式...");
        log.info("cronString:{}" , cronString);
        itemSyncScheduler.setCronString(cronString);
        itemSyncScheduler.executeTask();
    }
}

實現CommandLineRunner ,實現Springboot啟動載入

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component
@Order(1)
public class SchedulerTaskRunner implements CommandLineRunner {
    private ItemSyncScheduler itemSyncScheduler;
    @Autowired
    public SchedulerTaskRunner(ItemSyncScheduler itemSyncScheduler) {
        this.itemSyncScheduler= itemSyncScheduler;
    }
    @Override
    public void run(String... args) throws Exception {
        itemSyncScheduler.executeTask();
    }
}

歸納總結

基於上一版定時程式的問題,做了改進,加上了執行緒池和做到了動態觸發,網上的資料很多都是直接寫明使用SchedulingConfigurer來實現動態定時程式,不過很多都寫明場景,本文通過實際,寫明實現方法,本文是在儲存定時程式的時候,設定最新的cron表示式,調一下介面重新載入,還可以使用canal等中介軟體監聽資料表,如果改了就再設定cron表示式,然後觸發程式

到此這篇關於SpringBoot動態定時功能實現方案詳解的文章就介紹到這了,更多相關SpringBoot動態定時內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com