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

動態(tài)更改Spring定時(shí)任務(wù)Cron表達(dá)式的優(yōu)雅方案實(shí)例詳解

 更新時(shí)間:2022年12月22日 08:15:34   作者:程語有云  
spring定時(shí)器非常強(qiáng)大,但是有時(shí)候我們需要在不需要重啟應(yīng)用就可以動態(tài)的改變Cron表達(dá)式的值,下面這篇文章主要給大家介紹了關(guān)于動態(tài)更改Spring定時(shí)任務(wù)Cron表達(dá)式的優(yōu)雅方案,需要的朋友可以參考下

0x01 前言

在 SpringBoot 項(xiàng)目中,我們可以通過@EnableScheduling注解開啟調(diào)度任務(wù)支持,并通過@Scheduled注解快速地建立一系列定時(shí)任務(wù)。

@Scheduled支持下面三種配置執(zhí)行時(shí)間的方式:

  • cron(expression):根據(jù)Cron表達(dá)式來執(zhí)行。
  • fixedDelay(period):固定間隔時(shí)間執(zhí)行,無論任務(wù)執(zhí)行長短,兩次任務(wù)執(zhí)行的間隔總是相同的。
  • fixedRate(period):固定頻率執(zhí)行,從任務(wù)啟動之后,總是在固定的時(shí)刻執(zhí)行,如果因?yàn)閳?zhí)行時(shí)間過長,造成錯過某個時(shí)刻的執(zhí)行(晚點(diǎn)),則任務(wù)會被立刻執(zhí)行。

最常用的應(yīng)該是第一種方式,基于Cron表達(dá)式的執(zhí)行模式,因其相對來說更加靈活。

0x02 可變與不可變

默認(rèn)情況下,@Scheduled注解標(biāo)記的定時(shí)任務(wù)方法在初始化之后,是不會再發(fā)生變化的。Spring 在初始化 bean 后,通過后處理器攔截所有帶有@Scheduled注解的方法,并解析相應(yīng)的的注解參數(shù),放入相應(yīng)的定時(shí)任務(wù)列表等待后續(xù)統(tǒng)一執(zhí)行處理。到定時(shí)任務(wù)真正啟動之前,我們都有機(jī)會更改任務(wù)的執(zhí)行周期等參數(shù)。換言之,我們既可以通過application.properties配置文件配合@Value注解的方式指定任務(wù)的Cron表達(dá)式,亦可以通過CronTrigger從數(shù)據(jù)庫或者其他任意存儲中間件中加載并注冊定時(shí)任務(wù)。這是 Spring 提供給我們的可變的部分。

但是我們往往要得更多。能否在定時(shí)任務(wù)已經(jīng)在執(zhí)行過的情況下,去動態(tài)更改Cron表達(dá)式,甚至禁用某個定時(shí)任務(wù)呢?很遺憾,默認(rèn)情況下,這是做不到的,任務(wù)一旦被注冊和執(zhí)行,用于注冊的參數(shù)便被固定下來,這是不可變的部分。

0x03 創(chuàng)造與毀滅

既然創(chuàng)造之后不可變,那就毀滅之后再重建吧。于是乎,我們的思路便是,在注冊期間保留任務(wù)的關(guān)鍵信息,并通過另一個定時(shí)任務(wù)檢查配置是否發(fā)生變化,如果有變化,就把“前任”干掉,取而代之。如果沒有變化,就保持原樣。

先對任務(wù)做個簡單的抽象,方便統(tǒng)一的識別和管理:

public interface IPollableService {
    /**
     * 執(zhí)行方法
     */
    void poll();

    /**
     * 獲取周期表達(dá)式
     *
     * @return CronExpression
     */
    default String getCronExpression() {
        return null;
    }

    /**
     * 獲取任務(wù)名稱
     *
     * @return 任務(wù)名稱
     */
    default String getTaskName() {
        return this.getClass().getSimpleName();
    }
}

最重要的便是getCronExpression()方法,每個定時(shí)服務(wù)實(shí)現(xiàn)可以自己控制自己的表達(dá)式,變與不變,自己說了算。至于從何處獲取,怎么獲取,請諸君自行發(fā)揮了。接下來,就是實(shí)現(xiàn)任務(wù)的動態(tài)注冊:

@Configuration
@EnableAsync
@EnableScheduling
public class SchedulingConfiguration implements SchedulingConfigurer, ApplicationContextAware {
    private static final Logger log = LoggerFactory.getLogger(SchedulingConfiguration.class);
    private static ApplicationContext appCtx;
    private final ConcurrentMap<String, ScheduledTask> scheduledTaskHolder = new ConcurrentHashMap<>(16);
    private final ConcurrentMap<String, String> cronExpressionHolder = new ConcurrentHashMap<>(16);
    private ScheduledTaskRegistrar taskRegistrar;

    public static synchronized void setAppCtx(ApplicationContext appCtx) {
        SchedulingConfiguration.appCtx = appCtx;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        setAppCtx(applicationContext);
    }

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        this.taskRegistrar = taskRegistrar;
    }

    /**
     * 刷新定時(shí)任務(wù)表達(dá)式
     */
    public void refresh() {
        Map<String, IPollableService> beanMap = appCtx.getBeansOfType(IPollableService.class);
        if (beanMap.isEmpty() || taskRegistrar == null) {
            return;
        }
        beanMap.forEach((beanName, task) -> {
            String expression = task.getCronExpression();
            String taskName = task.getTaskName();
            if (null == expression) {
                log.warn("定時(shí)任務(wù)[{}]的任務(wù)表達(dá)式未配置或配置錯誤,請檢查配置", taskName);
                return;
            }
            // 如果策略執(zhí)行時(shí)間發(fā)生了變化,則取消當(dāng)前策略的任務(wù),并重新注冊任務(wù)
            boolean unmodified = scheduledTaskHolder.containsKey(beanName) && cronExpressionHolder.get(beanName).equals(expression);
            if (unmodified) {
                log.info("定時(shí)任務(wù)[{}]的任務(wù)表達(dá)式未發(fā)生變化,無需刷新", taskName);
                return;
            }
            Optional.ofNullable(scheduledTaskHolder.remove(beanName)).ifPresent(existTask -> {
                existTask.cancel();
                cronExpressionHolder.remove(beanName);
            });
            if (ScheduledTaskRegistrar.CRON_DISABLED.equals(expression)) {
                log.warn("定時(shí)任務(wù)[{}]的任務(wù)表達(dá)式配置為禁用,將被不會被調(diào)度執(zhí)行", taskName);
                return;
            }
            CronTask cronTask = new CronTask(task::poll, expression);
            ScheduledTask scheduledTask = taskRegistrar.scheduleCronTask(cronTask);
            if (scheduledTask != null) {
                log.info("定時(shí)任務(wù)[{}]已加載,當(dāng)前任務(wù)表達(dá)式為[{}]", taskName, expression);
                scheduledTaskHolder.put(beanName, scheduledTask);
                cronExpressionHolder.put(beanName, expression);
            }
        });
    }
}

重點(diǎn)是保存ScheduledTask對象的引用,它是控制任務(wù)啟停的關(guān)鍵。而表達(dá)式“-”則作為一個特殊的標(biāo)記,用于禁用某個定時(shí)任務(wù)。當(dāng)然,禁用后的任務(wù)通過重新賦予新的 Cron 表達(dá)式,是可以“復(fù)活”的。完成了上面這些,我們還需要一個定時(shí)任務(wù)來動態(tài)監(jiān)控和刷新定時(shí)任務(wù)配置:

@Component
public class CronTaskLoader implements ApplicationRunner {
    private static final Logger log = LoggerFactory.getLogger(CronTaskLoader.class);
    private final SchedulingConfiguration schedulingConfiguration;
    private final AtomicBoolean appStarted = new AtomicBoolean(false);
    private final AtomicBoolean initializing = new AtomicBoolean(false);

    public CronTaskLoader(SchedulingConfiguration schedulingConfiguration) {
        this.schedulingConfiguration = schedulingConfiguration;
    }

    /**
     * 定時(shí)任務(wù)配置刷新
     */
    @Scheduled(fixedDelay = 5000)
    public void cronTaskConfigRefresh() {
        if (appStarted.get() && initializing.compareAndSet(false, true)) {
            log.info("定時(shí)調(diào)度任務(wù)動態(tài)加載開始>>>>>>");
            try {
                schedulingConfiguration.refresh();
            } finally {
                initializing.set(false);
            }
            log.info("定時(shí)調(diào)度任務(wù)動態(tài)加載結(jié)束<<<<<<");
        }
    }

    @Override
    public void run(ApplicationArguments args) {
        if (appStarted.compareAndSet(false, true)) {
            cronTaskConfigRefresh();
        }
    }
}

當(dāng)然,也可以把這部分代碼直接整合到SchedulingConfiguration中,但是為了方便擴(kuò)展,這里還是將執(zhí)行與觸發(fā)分離了。畢竟除了通過定時(shí)任務(wù)觸發(fā)刷新,還可以在界面上通過按鈕手動觸發(fā)刷新,或者通過消息機(jī)制回調(diào)刷新。這一部分就請大家根據(jù)實(shí)際業(yè)務(wù)情況來自由發(fā)揮了。

0x04 驗(yàn)證

我們創(chuàng)建一個原型工程和三個簡單的定時(shí)任務(wù)來驗(yàn)證下,第一個任務(wù)是執(zhí)行周期固定的任務(wù),假設(shè)它的Cron表達(dá)式永遠(yuǎn)不會發(fā)生變化,像這樣:

@Service
public class CronTaskBar implements IPollableService {
    @Override
    public void poll() {
        System.out.println("Say Bar");
    }

    @Override
    public String getCronExpression() {
        return "0/1 * * * * ?";
    }
}

第二個任務(wù)是一個經(jīng)常更換執(zhí)行周期的任務(wù),我們用一個隨機(jī)數(shù)發(fā)生器來模擬它的善變:

@Service
public class CronTaskFoo implements IPollableService {
    private static final Random random = new SecureRandom();

    @Override
    public void poll() {
        System.out.println("Say Foo");
    }

    @Override
    public String getCronExpression() {
        return "0/" + (random.nextInt(9) + 1) + " * * * * ?";
    }
}

第三個任務(wù)就厲害了,它仿佛就像一個電燈的開關(guān),在啟用和禁用中反復(fù)橫跳:

@Service
public class CronTaskUnavailable implements IPollableService {
    private String cronExpression = "-";
    private static final Map<String, String> map = new HashMap<>();

    static {
        map.put("-", "0/1 * * * * ?");
        map.put("0/1 * * * * ?", "-");
    }

    @Override
    public void poll() {
        System.out.println("Say Unavailable");
    }

    @Override
    public String getCronExpression() {
        return (cronExpression = map.get(cronExpression));
    }
}

如果上面的步驟都做對了,日志里應(yīng)該能看到類似這樣的輸出:

定時(shí)調(diào)度任務(wù)動態(tài)加載開始>>>>>>
定時(shí)任務(wù)[CronTaskBar]的任務(wù)表達(dá)式未發(fā)生變化,無需刷新
定時(shí)任務(wù)[CronTaskFoo]已加載,當(dāng)前任務(wù)表達(dá)式為[0/6 * * * * ?]
定時(shí)任務(wù)[CronTaskUnavailable]的任務(wù)表達(dá)式配置為禁用,將被不會被調(diào)度執(zhí)行
定時(shí)調(diào)度任務(wù)動態(tài)加載結(jié)束<<<<<<
Say Bar
Say Bar
Say Foo
Say Bar
Say Bar
Say Bar
定時(shí)調(diào)度任務(wù)動態(tài)加載開始>>>>>>
定時(shí)任務(wù)[CronTaskBar]的任務(wù)表達(dá)式未發(fā)生變化,無需刷新
定時(shí)任務(wù)[CronTaskFoo]已加載,當(dāng)前任務(wù)表達(dá)式為[0/3 * * * * ?]
定時(shí)任務(wù)[CronTaskUnavailable]已加載,當(dāng)前任務(wù)表達(dá)式為[0/1 * * * * ?]
定時(shí)調(diào)度任務(wù)動態(tài)加載結(jié)束<<<<<<
Say Unavailable
Say Bar
Say Unavailable
Say Bar
Say Foo
Say Unavailable
Say Bar
Say Unavailable
Say Bar
Say Unavailable
Say Bar

0x05 小結(jié)

我們在上文通過定時(shí)刷新和重建任務(wù)的方式來實(shí)現(xiàn)了動態(tài)更改Cron表達(dá)式的需求,能夠滿足大部分的項(xiàng)目場景,而且沒有引入quartzs等額外的中間件,可以說是十分的輕量和優(yōu)雅了。

到此這篇關(guān)于動態(tài)更改Spring定時(shí)任務(wù)Cron表達(dá)式的優(yōu)雅方案的文章就介紹到這了,更多相關(guān)動態(tài)更改Spring定時(shí)任務(wù)Cron表達(dá)式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java?常量池詳解之class文件常量池?和class運(yùn)行時(shí)常量池

    Java?常量池詳解之class文件常量池?和class運(yùn)行時(shí)常量池

    這篇文章主要介紹了Java?常量池詳解之class文件常量池?和class運(yùn)行時(shí)常量池,常量池主要存放兩大類常量:字面量,符號引用,本文結(jié)合示例代碼對java class常量池相關(guān)知識介紹的非常詳細(xì),需要的朋友可以參考下
    2022-12-12
  • SpringBoot實(shí)現(xiàn)Server-Sent Events(SSE)的使用完整指南

    SpringBoot實(shí)現(xiàn)Server-Sent Events(SSE)的使用完整指南

    使用SpringBoot實(shí)現(xiàn)Server-Sent Events(SSE)可以有效處理實(shí)時(shí)數(shù)據(jù)推送需求,具有單向通信、輕量級和高實(shí)時(shí)性等優(yōu)勢,本文詳細(xì)介紹了在SpringBoot中創(chuàng)建SSE端點(diǎn)的步驟,并通過代碼示例展示了客戶端如何接收數(shù)據(jù),適用于實(shí)時(shí)通知、數(shù)據(jù)展示和在線聊天等場景
    2024-09-09
  • springboot 事件監(jiān)聽的實(shí)現(xiàn)方法

    springboot 事件監(jiān)聽的實(shí)現(xiàn)方法

    這篇文章主要介紹了springboot 事件監(jiān)聽的實(shí)現(xiàn)方法,并詳細(xì)的介紹了四種監(jiān)聽方式,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-04-04
  • SpringBoot整合MybatisPlusGernerator實(shí)現(xiàn)逆向工程

    SpringBoot整合MybatisPlusGernerator實(shí)現(xiàn)逆向工程

    在我們寫項(xiàng)目的時(shí)候,我們時(shí)常會因?yàn)樾枰獎?chuàng)建很多的項(xiàng)目結(jié)構(gòu)而頭疼,本文主要介紹了SpringBoot整合MybatisPlusGernerator實(shí)現(xiàn)逆向工程,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-05-05
  • javaweb中Http協(xié)議詳解

    javaweb中Http協(xié)議詳解

    HTTP是hypertext transfer protocol(超文本傳輸協(xié)議)的簡寫,它是TCP/IP協(xié)議的一個應(yīng)用層協(xié)議,用于定義WEB瀏覽器與WEB服務(wù)器之間交換數(shù)據(jù)的過程。這篇文章主要為大家詳細(xì)介紹了javaweb中的Http協(xié)議,感興趣的小伙伴們可以參考一下
    2016-05-05
  • Java內(nèi)部類的使用教程詳解

    Java內(nèi)部類的使用教程詳解

    在Java中,有些類可以被定義在另一個類的內(nèi)部,我們把在一個類里面定義的類稱為內(nèi)部類。本文主要介紹了Java內(nèi)部類的使用,需要的可以參考一下
    2023-04-04
  • Java swing實(shí)現(xiàn)支持錄音等功能的鋼琴程序

    Java swing實(shí)現(xiàn)支持錄音等功能的鋼琴程序

    這篇文章主要為大家詳細(xì)介紹了Java swing實(shí)現(xiàn)鋼琴程序,支持錄音等功能的Java鋼琴源碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-06-06
  • Spring?AOPr如何打通兩個切面之間的通信

    Spring?AOPr如何打通兩個切面之間的通信

    本文主要介紹了Spring?AOPr如何打通兩個切面之間的通信,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-03-03
  • JAVA對象JSON數(shù)據(jù)互相轉(zhuǎn)換的四種常見情況

    JAVA對象JSON數(shù)據(jù)互相轉(zhuǎn)換的四種常見情況

    這篇文章主要介紹了JAVA對象JSON數(shù)據(jù)互相轉(zhuǎn)換的四種常見情況,需要的朋友可以參考下
    2014-04-04
  • Spring Security保護(hù)用戶密碼常用方法詳解

    Spring Security保護(hù)用戶密碼常用方法詳解

    這篇文章主要介紹了Spring Security保護(hù)用戶密碼常用方法詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-09-09

最新評論

余庆县| 昆山市| 武功县| 沙洋县| 荣昌县| 德兴市| 霍山县| 林芝县| 乐山市| 怀宁县| 永胜县| 海阳市| 通州区| 新化县| 沙湾县| 绍兴县| 德钦县| 富阳市| 普安县| 体育| 响水县| 拉萨市| 通州市| 泗洪县| 治县。| 六盘水市| 宿松县| 杨浦区| 巨野县| 中宁县| 河东区| 荣成市| 九龙坡区| 永寿县| 万山特区| 册亨县| 岳阳县| 塔河县| 七台河市| 江都市| 阿坝|