日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不

當(dāng)前位置:首頁 > 科技  > 軟件

Spring中Cron表達(dá)式的優(yōu)雅實(shí)現(xiàn)方案

來源: 責(zé)編: 時(shí)間:2024-03-18 09:42:48 173觀看
導(dǎo)讀在 SpringBoot 項(xiàng)目中,我們可以通過@EnableScheduling注解開啟調(diào)度任務(wù)支持,并通過@Scheduled注解快速地建立一系列定時(shí)任務(wù)。@Scheduled支持下面三種配置執(zhí)行時(shí)間的方式:cron(expression):根據(jù)Cron表達(dá)式來執(zhí)行。fixedDe

在 SpringBoot 項(xiàng)目中,我們可以通過@EnableScheduling注解開啟調(diào)度任務(wù)支持,并通過@Scheduled注解快速地建立一系列定時(shí)任務(wù)。0Kt28資訊網(wǎng)——每日最新資訊28at.com

@Scheduled支持下面三種配置執(zhí)行時(shí)間的方式:0Kt28資訊網(wǎng)——每日最新資訊28at.com

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

最常用的應(yīng)該是第一種方式,基于Cron表達(dá)式的執(zhí)行模式,因其相對(duì)來說更加靈活。0Kt28資訊網(wǎng)——每日最新資訊28at.com

可變與不可變

默認(rèn)情況下,@Scheduled注解標(biāo)記的定時(shí)任務(wù)方法在初始化之后,是不會(huì)再發(fā)生變化的。Spring 在初始化 bean 后,通過后處理器攔截所有帶有@Scheduled注解的方法,并解析相應(yīng)的的注解參數(shù),放入相應(yīng)的定時(shí)任務(wù)列表等待后續(xù)統(tǒng)一執(zhí)行處理。到定時(shí)任務(wù)真正啟動(dòng)之前,我們都有機(jī)會(huì)更改任務(wù)的執(zhí)行周期等參數(shù)。0Kt28資訊網(wǎng)——每日最新資訊28at.com

換言之,我們既可以通過application.properties配置文件配合@Value注解的方式指定任務(wù)的Cron表達(dá)式,亦可以通過CronTrigger從數(shù)據(jù)庫或者其他任意存儲(chǔ)中間件中加載并注冊(cè)定時(shí)任務(wù)。這是 Spring 提供給我們的可變的部分。0Kt28資訊網(wǎng)——每日最新資訊28at.com

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

創(chuàng)造與毀滅

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

先對(duì)任務(wù)做個(gè)簡(jiǎn)單的抽象,方便統(tǒng)一的識(shí)別和管理:0Kt28資訊網(wǎng)——每日最新資訊28at.com

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()方法,每個(gè)定時(shí)服務(wù)實(shí)現(xiàn)可以自己控制自己的表達(dá)式,變與不變,自己說了算。至于從何處獲取,怎么獲取,請(qǐng)諸君自行發(fā)揮了。接下來,就是實(shí)現(xiàn)任務(wù)的動(dòng)態(tài)注冊(cè):0Kt28資訊網(wǎng)——每日最新資訊28at.com

@Configuration@EnableAsync@EnableSchedulingpublic 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á)式未配置或配置錯(cuò)誤,請(qǐng)檢查配置", taskName);                return;            }            // 如果策略執(zhí)行時(shí)間發(fā)生了變化,則取消當(dāng)前策略的任務(wù),并重新注冊(cè)任務(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á)式配置為禁用,將被不會(huì)被調(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對(duì)象的引用,它是控制任務(wù)啟停的關(guān)鍵。而表達(dá)式“-”則作為一個(gè)特殊的標(biāo)記,用于禁用某個(gè)定時(shí)任務(wù)。0Kt28資訊網(wǎng)——每日最新資訊28at.com

當(dāng)然,禁用后的任務(wù)通過重新賦予新的 Cron 表達(dá)式,是可以“復(fù)活”的。完成了上面這些,我們還需要一個(gè)定時(shí)任務(wù)來動(dòng)態(tài)監(jiān)控和刷新定時(shí)任務(wù)配置:0Kt28資訊網(wǎng)——每日最新資訊28at.com

@Componentpublic 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ù)動(dòng)態(tài)加載開始>>>>>>");            try {                schedulingConfiguration.refresh();            } finally {                initializing.set(false);            }            log.info("定時(shí)調(diào)度任務(wù)動(dòng)態(tài)加載結(jié)束<<<<<<");        }    }    @Override    public void run(ApplicationArguments args) {        if (appStarted.compareAndSet(false, true)) {            cronTaskConfigRefresh();        }    }}

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

驗(yàn)證

我們創(chuàng)建一個(gè)原型工程和三個(gè)簡(jiǎn)單的定時(shí)任務(wù)來驗(yàn)證下,第一個(gè)任務(wù)是執(zhí)行周期固定的任務(wù),假設(shè)它的Cron表達(dá)式永遠(yuǎn)不會(huì)發(fā)生變化,像這樣:0Kt28資訊網(wǎng)——每日最新資訊28at.com

@Servicepublic class CronTaskBar implements IPollableService {    @Override    public void poll() {        System.out.println("Say Bar");    }    @Override    public String getCronExpression() {        return "0/1 * * * * ?";    }}

第二個(gè)任務(wù)是一個(gè)經(jīng)常更換執(zhí)行周期的任務(wù),我們用一個(gè)隨機(jī)數(shù)發(fā)生器來模擬它的善變:0Kt28資訊網(wǎng)——每日最新資訊28at.com

@Servicepublic 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) + " * * * * ?";    }}

第三個(gè)任務(wù)就厲害了,它仿佛就像一個(gè)電燈的開關(guān),在啟用和禁用中反復(fù)橫跳:0Kt28資訊網(wǎng)——每日最新資訊28at.com

@Servicepublic 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));    }}

如果上面的步驟都做對(duì)了,日志里應(yīng)該能看到類似這樣的輸出:0Kt28資訊網(wǎng)——每日最新資訊28at.com

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

小結(jié)

我們?cè)谏衔耐ㄟ^定時(shí)刷新和重建任務(wù)的方式來實(shí)現(xiàn)了動(dòng)態(tài)更改Cron表達(dá)式的需求,能夠滿足大部分的項(xiàng)目場(chǎng)景,而且沒有引入quartzs等額外的中間件,可以說是十分的輕量和優(yōu)雅了。0Kt28資訊網(wǎng)——每日最新資訊28at.com

本文鏈接:http://www.www897cc.com/showinfo-26-76560-0.htmlSpring中Cron表達(dá)式的優(yōu)雅實(shí)現(xiàn)方案

聲明:本網(wǎng)頁內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。郵件:2376512515@qq.com

上一篇: 我們一起聊聊如何保證接口冪等性?高并發(fā)下的接口冪等性如何實(shí)現(xiàn)?

下一篇: Spring事件如何異步執(zhí)行?

標(biāo)簽:
  • 熱門焦點(diǎn)
Top 主站蜘蛛池模板: 登封市| 河西区| 田阳县| 商城县| 凤台县| 拜泉县| 连云港市| 丹寨县| 中西区| 鄄城县| 奉贤区| 革吉县| 会宁县| 北流市| 涿鹿县| 绵竹市| 枣强县| 宜城市| 弥渡县| 盐城市| 临沂市| 阳泉市| 江源县| 张家港市| 乌拉特中旗| 承德市| 孝义市| 宜君县| 高要市| 中方县| 平乐县| 茂名市| 华蓥市| 雅安市| 凉城县| 肇东市| 东乌| 邹平县| 准格尔旗| 工布江达县| 青河县|