SpringBoot動(dòng)態(tài)定時(shí)功能實(shí)現(xiàn)方案詳解
業(yè)務(wù)場(chǎng)景
基于上篇程序,做了一版動(dòng)態(tài)定時(shí)程序,然后發(fā)現(xiàn)這個(gè)定時(shí)程序需要在下次執(zhí)行的時(shí)候會(huì)加載新的時(shí)間,所以如果改了定時(shí)程序不能馬上觸發(fā),所以想到一種方法,在保存定時(shí)程序的時(shí)候?qū)ron表達(dá)式傳過去,然后觸發(fā)定時(shí)程序,下面看看怎么實(shí)現(xiàn)
環(huán)境準(zhǔn)備
開發(fā)環(huán)境
- JDK 1.8
- SpringBoot2.2.1
- Maven 3.2+
開發(fā)工具
- IntelliJ IDEA
- smartGit
- Navicat15
實(shí)現(xiàn)方案
基于上一版進(jìn)行改進(jìn):
- 先根據(jù)選擇的星期生成cron表達(dá)式,保存到數(shù)據(jù)庫(kù)里,同時(shí)更改cron表達(dá)式手動(dòng)觸發(fā)定時(shí)程序加載最新的cron表達(dá)式
- 根據(jù)保存的cron表達(dá)式規(guī)則執(zhí)行定時(shí)程序
- 通過CommandLineRunner設(shè)置啟動(dòng)加載
- 加上線程池,提高線程復(fù)用率和程序性能
加上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("異步任務(wù)異常,message {} , method {} , params" , throwable , method , objects); }; } }
加上一個(gè)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 = () -> { // 執(zhí)行業(yè)務(wù) 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表達(dá)式異常,已經(jīng)啟用默認(rèn)配置"); // 配置cron表達(dá)式異常,執(zhí)行默認(rèn)的表達(dá)式 trigger = new CronTrigger(getDefaultCron()); return trigger.nextExecutionTime(triggerContext); } } }; threadPoolTaskScheduler.schedule(task , trigger); } protected abstract String getCronString(); protected abstract void doBusiness(); protected abstract String getDefaultCron(); }
實(shí)現(xiàn)類,基于自己的業(yè)務(wù)實(shí)現(xiàn),然后事項(xiàng)抽象類,通過模板模式進(jìn)行編程
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("執(zhí)行業(yè)務(wù)..."); log.info("執(zhí)行時(shí)間:{}" , LocalDateTime.now()); // 執(zhí)行業(yè)務(wù) } @Override protected String getDefaultCron() { return defaultCron; } }
如果更改了cron表達(dá)式,程序不會(huì)馬上觸發(fā),所以直接開放一個(gè)接口出來,調(diào)用的時(shí)候,設(shè)置最新的表達(dá)式,然后重新調(diào)用定時(shí)程序
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表達(dá)式") public void updateItemCron(@RequestParam("cronString") String cronString) { log.info("更新cron表達(dá)式..."); log.info("cronString:{}" , cronString); itemSyncScheduler.setCronString(cronString); itemSyncScheduler.executeTask(); } }
實(shí)現(xiàn)CommandLineRunner ,實(shí)現(xiàn)Springboot啟動(dòng)加載
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(); } }
歸納總結(jié)
基于上一版定時(shí)程序的問題,做了改進(jìn),加上了線程池和做到了動(dòng)態(tài)觸發(fā),網(wǎng)上的資料很多都是直接寫明使用SchedulingConfigurer
來實(shí)現(xiàn)動(dòng)態(tài)定時(shí)程序,不過很多都寫明場(chǎng)景,本文通過實(shí)際,寫明實(shí)現(xiàn)方法,本文是在保存定時(shí)程序的時(shí)候,設(shè)置最新的cron表達(dá)式,調(diào)一下接口重新加載,還可以使用canal等中間件監(jiān)聽數(shù)據(jù)表,如果改了就再設(shè)置cron表達(dá)式,然后觸發(fā)程序
到此這篇關(guān)于SpringBoot動(dòng)態(tài)定時(shí)功能實(shí)現(xiàn)方案詳解的文章就介紹到這了,更多相關(guān)SpringBoot動(dòng)態(tài)定時(shí)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用Spring?Boot如何限制在一分鐘內(nèi)某個(gè)IP只能訪問10次
有些時(shí)候,為了防止我們上線的網(wǎng)站被攻擊,或者被刷取流量,我們會(huì)對(duì)某一個(gè)ip進(jìn)行限制處理,這篇文章,我們將通過Spring?Boot編寫一個(gè)小案例,來實(shí)現(xiàn)在一分鐘內(nèi)同一個(gè)IP只能訪問10次,感興趣的朋友一起看看吧2023-10-10詳談ThreadLocal-單例模式下高并發(fā)線程安全
這篇文章主要介紹了ThreadLocal-單例模式下高并發(fā)線程安全,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09slf4j?jcl?jul?log4j1?log4j2?logback各組件系統(tǒng)日志切換
這篇文章主要介紹了slf4j、jcl、jul、log4j1、log4j2、logback的大總結(jié),各個(gè)組件的jar包以及目前系統(tǒng)日志需要切換實(shí)現(xiàn)方式的方法,有需要的朋友可以借鑒參考下2022-03-03FeignClient如何通過配置變量調(diào)用配置文件url
這篇文章主要介紹了FeignClient如何通過配置變量調(diào)用配置文件url,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06圖解Spring框架的設(shè)計(jì)理念與設(shè)計(jì)模式
這篇文章主要通過多圖詳細(xì)解釋Spring框架的設(shè)計(jì)理念與設(shè)計(jì)模式,需要的朋友可以參考下2015-08-08SpringCloud使用Zookeeper作為注冊(cè)中心
這篇文章主要介紹了SpringCloud如何使用Zookeeper作為注冊(cè)中心,幫助大家更好的理解和學(xué)習(xí)使用Zookeeper,感興趣的朋友可以了解下2021-04-04