最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

基于Nacos實(shí)現(xiàn)SpringBoot動態(tài)定時任務(wù)調(diào)度

 更新時間:2025年11月16日 09:21:42   作者:gelald  
本文主要介紹了在Spring Boot項目中使用Spring Scheduling實(shí)現(xiàn)定時任務(wù),并通過Nacos動態(tài)配置Cron表達(dá)式實(shí)現(xiàn)任務(wù)的動態(tài)變更,通過實(shí)現(xiàn)SchedulingConfigurer接口和訂閱Nacos配置變更事件,成功實(shí)現(xiàn)了任務(wù)的動態(tài)調(diào)度和暫停

背景

最近在項目開發(fā)上,有一個定時核對并清理的需求,定時規(guī)則較為簡單,每15分鐘運(yùn)行一次,并且項目中暫未接入分布式定時任務(wù)調(diào)度框架;鑒于以上兩個原因,我決定直接用 Spring scheduling 開干。

回顧一下 SpringBoot 項目中定義定時任務(wù),其實(shí)就幾個步驟:

  • 在啟動類上,或者任意一個配置類上添加 @EnableScheduling 注解
  • 在需要運(yùn)行定時任務(wù)的方法上,添加 @Scheduled 注解, 可以傳入 cronfixedDelay、fixedRate三個值之一
    • cron: 傳入 Spring cron 表達(dá)式,推薦使用
    • fixedDelay: 以調(diào)用完成時刻開始計算間隔時間,單位是毫秒
    • fixedRate: 以調(diào)用開始時刻來計算間隔時間,單位是毫秒
  • 在這個方法內(nèi)調(diào)用定時任務(wù)實(shí)際的執(zhí)行邏輯

我用的cron表達(dá)式很快就把這個需求實(shí)現(xiàn)了;但這時變數(shù)出現(xiàn)了,要求cron表達(dá)式支持 Nacos 動態(tài)配置,并且「動態(tài)配置后,不需要重啟服務(wù)也能生效」

讓我一時間犯了難,后面查詢相關(guān)資料后,也實(shí)現(xiàn)了這個新需求,接下來我分享一下解題過程

實(shí)現(xiàn)動態(tài)變更定時機(jī)制

配置化 cron 表達(dá)式

我們知道,如果一個 Java Bean 使用 @ConfigurationProperties 修飾后,當(dāng)它的配置值通過 Nacos 修改后,Nacos client 會收到變更事件,從而刷新對應(yīng)的屬性值,所以我們考慮把 cron 表達(dá)式放入一個 properties 類管理,并使用 @ConfigurationProperties 修飾

@Data
@Component
@ConfigurationProperties(prefix = "task-schedule")
public class TaskProperties {
    private String cronExpression = "*/5 * * * * ?";
}

然后我們可以把這個 cron 表達(dá)式通過 placeholder 綁定到 @Schedule 的 cron 中

@Slf4j
@Component
public class SimpleTask {
    @Scheduled(cron = "#{@taskProperties.cronExpression}")
    public void simpleTask2() {
        log.info("SimpleTask2 scheduled");
    }
}

Spring schedule 調(diào)度規(guī)則

但是啟動項目之后發(fā)現(xiàn),無論怎么修改 Nacos 上的配置值,這個定時任務(wù)的執(zhí)行間隔都不會隨配置值更新,只會按照首次啟動時的 cron 表達(dá)式來進(jìn)行

翻看源碼 ScheduledAnnotationBeanPostProcessor 這個類的 postProcessAfterInitialization 方法

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
    ...
    Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
    if (!this.nonAnnotatedClasses.contains(targetClass) &&
            AnnotationUtils.isCandidateClass(targetClass, List.of(Scheduled.class, Schedules.class))) {
        Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
                (MethodIntrospector.MetadataLookup<Set<Scheduled>>) method -> {
                    Set<Scheduled> scheduledAnnotations = AnnotatedElementUtils.getMergedRepeatableAnnotations(
                            method, Scheduled.class, Schedules.class);
                    return (!scheduledAnnotations.isEmpty() ? scheduledAnnotations : null);
                });

            // Non-empty set of methods
            annotatedMethods.forEach((method, scheduledAnnotations) ->
                    scheduledAnnotations.forEach(scheduled -> processScheduled(scheduled, method, bean)));
    ...
    }
    return bean;
}

我們知道 BeanPostProcessor 中的 postProcessAfterInitialization 方法,在 Bean 初始化后執(zhí)行,看到它在逐一地調(diào)度這些定時任務(wù)

所以我們可以推斷,這些定時任務(wù)只會在項目啟動之后統(tǒng)一的調(diào)度,并不會在運(yùn)行期間自動更新調(diào)度規(guī)則

追蹤定時任務(wù)調(diào)度

既然我們查看源碼后發(fā)現(xiàn) SpringBoot 幫我們調(diào)度只發(fā)生在項目啟動之后,那么如果我們希望響應(yīng) Nacos 變更而改變調(diào)度規(guī)則,我們需要把調(diào)度的主動權(quán)把握在手

我們繼續(xù)追蹤源碼,ScheduledAnnotationBeanPostProcessorprocessScheduled 方法,調(diào)用 processScheduledSync

protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
    ...
    processScheduledSync(scheduled, method, bean);
}

private void processScheduledSync(Scheduled scheduled, Method method, Object bean) {
    Runnable task;
    try {
        task = createRunnable(bean, method, scheduled.scheduler());
    }
    catch (IllegalArgumentException ex) {
        ...
    }
    processScheduledTask(scheduled, task, method, bean);
}

我們目前先看同步調(diào)用的情況,可以看到 processScheduledSync 方法中,做了兩件事:

  • 創(chuàng)建一個 Runnable 對象
  • 調(diào)度這個 Runnable 對象

可以得到一點(diǎn)啟發(fā):定時任務(wù)相當(dāng)于創(chuàng)建一個任務(wù),然后使用調(diào)度器按規(guī)則不停地調(diào)用,這里的調(diào)度器我們可以簡單理解為 Scheduled 線程池

如果我們可以拿到這個定時任務(wù)執(zhí)行的調(diào)度器,緩存起來,這樣調(diào)度的時機(jī)、調(diào)度的規(guī)則都可以由我們自己控制了;而創(chuàng)建一個 Runnable 對象其實(shí)也不難,可以簡單創(chuàng)建后直接把定時任務(wù)包裝起來

查看 processScheduledTask 方法,它確實(shí)是這么做的

private void processScheduledTask(Scheduled scheduled, Runnable runnable, Method method, Object bean) {
    try {
        ...
        // Determine initial delay
        ...

        // Check cron expression
        String cron = scheduled.cron();
        if (StringUtils.hasText(cron)) {
            String zone = scheduled.zone();
            if (this.embeddedValueResolver != null) {
                cron = this.embeddedValueResolver.resolveStringValue(cron);
                zone = this.embeddedValueResolver.resolveStringValue(zone);
            }
            if (StringUtils.hasLength(cron)) {
                Assert.isTrue(initialDelay.isNegative(), "'initialDelay' not supported for cron triggers");
                processedSchedule = true;
                if (!Scheduled.CRON_DISABLED.equals(cron)) {
                    CronTrigger trigger;
                    if (StringUtils.hasText(zone)) {
                        trigger = new CronTrigger(cron, StringUtils.parseTimeZoneString(zone));
                    }
                    else {
                        trigger = new CronTrigger(cron);
                    }
                    tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, trigger)));
                }
            }
        }

        // Check fixed delay
        ...

        // Check fixed rate
        ...

        // Finally register the scheduled tasks
        synchronized (this.scheduledTasks) {
            Set<ScheduledTask> regTasks = this.scheduledTasks.computeIfAbsent(bean, key -> new LinkedHashSet<>(4));
            regTasks.addAll(tasks);
        }
    }
    ...
}

this.registrar.scheduleCronTask(new CronTask(runnable, trigger)),這個 this.registrar 的類型是 ScheduledTaskRegistrar,這一句我們可以知道 ScheduledTaskRegistrar 就是核心的調(diào)度器,負(fù)責(zé)調(diào)度各種定時任務(wù),比如:Cron、FixDelay 等等

它依靠它里面的 TaskScheduler 來負(fù)責(zé)執(zhí)行最終的任務(wù)調(diào)度,默認(rèn)的實(shí)現(xiàn)類是 ThreadPoolTaskScheduler,它依靠 ScheduledExecutorService 來最終實(shí)現(xiàn)定時任務(wù)的調(diào)度

掌握調(diào)度主動權(quán)

繼續(xù)翻閱源碼,我們發(fā)現(xiàn) SchedulingConfigurer 接口,它允許我們使用 ScheduledTaskRegistrar 來定制化定時任務(wù)的執(zhí)行,有了上面的一點(diǎn)鋪墊后,這個接口就很好理解了

@FunctionalInterface
public interface SchedulingConfigurer {

	/**
	 * Callback allowing a {@link org.springframework.scheduling.TaskScheduler}
	 * and specific {@link org.springframework.scheduling.config.Task} instances
	 * to be registered against the given the {@link ScheduledTaskRegistrar}.
	 * @param taskRegistrar the registrar to be configured
	 */
	void configureTasks(ScheduledTaskRegistrar taskRegistrar);
}

我們可以拿到方法參數(shù)的 taskRegistrar,像源碼一樣 scheduleCronTask

這種方式和使用 @schedule 相比,更靈活,但是代碼也會更為復(fù)雜一點(diǎn),屬于編程式調(diào)度

并且還可以使用一個成員屬性來接收這個 ScheduledTaskRegistrar,這樣可以隨時使用

protected ScheduledTaskRegistrar taskRegistrarHolder;

@Override
public void configureTasks(@Nonnull ScheduledTaskRegistrar taskRegistrar) {
    // set ScheduledTaskRegistrar so that we can use it to schedule the timer task
    taskRegistrarHolder = taskRegistrar;
}

訂閱 Nacos 配置更新事件

對于剛才定義的 Properties 類,即使 Nacos 可以幫我們刷新配置,但是不做任何的自定義的話,只是單純刷新對象屬性值也起不到動態(tài)的目的,所以我們需要訂閱并做出自定義的響應(yīng)邏輯

通過查閱資料找到了關(guān)鍵組件 NacosConfigManager,可以通過這樣的方式來訂閱配置變更,并且方法上還需要訂閱 ApplicationReadyEvent

@EventListener(ApplicationReadyEvent.class)
public void refreshConfig() throws Exception {
    // add nacos config listener
    nacosConfigManager.getConfigService().addListener("dynamic-schedule.yml", "DEFAULT_GROUP", new AbstractConfigChangeListener() {
        @Override
        public void receiveConfigChange(ConfigChangeEvent event) {
            // receive config change event
            ...
        }
    });
    log.info("added nacos config listener successfully, especially for cron-expression");
}

由于這種方式是訂閱整個配置文件的配置變更,每一個配置被更新都會執(zhí)行這個 refreshConfig 方法,所以在里面需要根據(jù)具體的配置項來監(jiān)聽

Collection<ConfigChangeItem> changeItems = event.getChangeItems();
for (ConfigChangeItem changeItem : changeItems) {
    log.info("config changed item: {}", changeItem.getKey());
    String key = changeItem.getKey();
    if (!("task-schedule.cron-expression".equals(key) || "task-schedule.cronExpression".equals(key))) {
        log.info("cron-expression has not been changed, doesn't response it");
        return false;
    }
    log.info("schedule config changed, new cron-expression: {}, now refresh the timer task", changeItem.getNewValue());
    return true;
}

至此,我們就實(shí)現(xiàn)了配置自定義監(jiān)聽,并且監(jiān)聽后我們還能做出其他后續(xù)的響應(yīng)邏輯

最終效果

有了上面自定義調(diào)度和自定義訂閱之后,我們只需要把他們整合一下,就能實(shí)現(xiàn)

另外針對任務(wù)暫停,這里也做簡單的演示

  • 初始的 cron 表達(dá)式是 */5 * * * * ?,項目啟動:

  • 修改 Nacos 配置,改成 */8 * * * * ?,并且不重啟服務(wù)

  • 修改 Nacos 配置,改成 -,讓任務(wù)停止,并且不重啟服務(wù)

    在 Spring schedule 中,如果 cron 表達(dá)式是一個 - 也是表示停止

到此這篇關(guān)于基于Nacos實(shí)現(xiàn)SpringBoot動態(tài)定時任務(wù)調(diào)度的文章就介紹到這了,更多相關(guān)SpringBoot動態(tài)定時任務(wù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java選擇結(jié)構(gòu)與循環(huán)結(jié)構(gòu)的使用詳解

    Java選擇結(jié)構(gòu)與循環(huán)結(jié)構(gòu)的使用詳解

    循環(huán)結(jié)構(gòu)是指在程序中需要反復(fù)執(zhí)行某個功能而設(shè)置的一種程序結(jié)構(gòu)。它由循環(huán)體中的條件,判斷繼續(xù)執(zhí)行某個功能還是退出循環(huán),選擇結(jié)構(gòu)用于判斷給定的條件,根據(jù)判斷的結(jié)果判斷某些條件,根據(jù)判斷的結(jié)果來控制程序的流程
    2022-03-03
  • Rxjava+Retrofit+MVP實(shí)現(xiàn)購物車功能

    Rxjava+Retrofit+MVP實(shí)現(xiàn)購物車功能

    這篇文章主要為大家詳細(xì)介紹了Rxjava+Retrofit+MVP實(shí)現(xiàn)購物車功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-05-05
  • Java 十大排序算法之計數(shù)排序刨析

    Java 十大排序算法之計數(shù)排序刨析

    計數(shù)排序是一個非基于比較的排序算法,該算法于1954年由 Harold H. Seward 提出。它的優(yōu)勢在于在對一定范圍內(nèi)的整數(shù)排序時,它的復(fù)雜度為Ο(n+k)(其中k是整數(shù)的范圍),快于任何比較排序算法
    2021-11-11
  • Shiro集成Spring之注解示例詳解

    Shiro集成Spring之注解示例詳解

    Shiro想必大家都知道了,是目前使用率要比spring security都要多的一個權(quán)限框架,下面這篇文章主要給大家介紹了關(guān)于Shiro集成Spring之注解的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2018-09-09
  • Mybatis+Druid+MybatisPlus多數(shù)據(jù)源配置方法

    Mybatis+Druid+MybatisPlus多數(shù)據(jù)源配置方法

    在項目開發(fā)中,經(jīng)常需要連接多個數(shù)據(jù)庫,使用Mybatis、Druid和MybatisPlus可以實(shí)現(xiàn)多數(shù)據(jù)源配置,通過定義配置類和修改配置文件,如properties或yaml,可以設(shè)置多個數(shù)據(jù)源,本文介紹了配置項包括Druid基本配置、數(shù)據(jù)源一、數(shù)據(jù)源二,感興趣的朋友一起看看吧
    2024-09-09
  • Java函數(shù)式編程之通過行為參數(shù)化傳遞代碼

    Java函數(shù)式編程之通過行為參數(shù)化傳遞代碼

    行為參數(shù)化就是可以幫助你處理頻繁變更的需求的一種軟件開發(fā)模式,這篇文章將給大家詳細(xì)的介紹一下Java函數(shù)式編程之行為參數(shù)化傳遞代碼,感興趣的同學(xué)可以參考閱讀下
    2023-08-08
  • MyBatisPlus深入探究映射匹配的兼容性

    MyBatisPlus深入探究映射匹配的兼容性

    在最近的工作中,碰到一個比較復(fù)雜的返回結(jié)果,發(fā)現(xiàn)簡單映射已經(jīng)解決不了這個問題了,只好去求助百度,學(xué)習(xí)mybatis映射匹配應(yīng)該怎么寫,將學(xué)習(xí)筆記結(jié)合工作碰到的問題寫下本文,供自身查漏補(bǔ)缺,同時已被不時之需
    2022-08-08
  • Java命令行下Jar包打包小結(jié)

    Java命令行下Jar包打包小結(jié)

    這篇文章主要介紹了Java命令行下Jar包打包小結(jié),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-12-12
  • Java運(yùn)用SWT插件編寫桌面記事本應(yīng)用程序

    Java運(yùn)用SWT插件編寫桌面記事本應(yīng)用程序

    這篇文章主要為大家介紹了一個Java項目實(shí)戰(zhàn),一步步教你實(shí)現(xiàn)記事本,步驟很詳細(xì),運(yùn)用SWT插件手把手編寫記事本,感興趣的小伙伴們可以參考一下
    2016-01-01
  • Java實(shí)現(xiàn)的各種排序算法(插入排序、選擇排序算法、冒泡排序算法)

    Java實(shí)現(xiàn)的各種排序算法(插入排序、選擇排序算法、冒泡排序算法)

    本文是小編給大家?guī)淼膉ava各種排序算法知識,包括插入排序、選擇排序算法、冒泡排序算法,代碼簡單易懂,需要的朋友可以參考下
    2016-08-08

最新評論

长春市| 抚顺县| 临沭县| 苍梧县| 龙游县| 德钦县| 广宁县| 丰原市| 虎林市| 日照市| 夏河县| 广东省| 清河县| 河曲县| 泗阳县| 洛宁县| 奇台县| 景宁| 铜鼓县| 灵台县| 班玛县| 高邑县| 新绛县| 嘉荫县| 保亭| 井研县| 边坝县| 石阡县| 武陟县| 清流县| 庐江县| 乌拉特后旗| 乌拉特前旗| 长泰县| 东城区| 庆云县| 房山区| 河南省| 巴青县| 阿城市| 合江县|