不提你可能不知道,spring定时任务的数字星期域不符合常规的cron定义

简介: 大家都知道,使用Spring的定时任务非常的简单方便,只需要在配置类上添加@EnableScheduling注解,同时在定时方法上添加@Scheduled(cron = "* * 1 * * *")便可以设置一个每天1点定时跑的任务

大家都知道,使用Spring的定时任务非常的简单方便,只需要在配置类上添加@EnableScheduling注解,同时在定时方法上添加@Scheduled(cron = "* * 1 * * *")便可以设置一个每天1点定时跑的任务。


当然,本文不是为了介绍Schedule定时器的用法的,这个网上一大堆,就不重复造轮子了。为了说说强哥在使用Spring定时任务遇到的问题,需要先简单介绍下cron表达式:


一个cron表达式有至少6个(也可能7个)有空格分隔的时间元素。 按顺序依次为 秒(0~59) 分钟(0~59) 小时(0~23) 天(月)(0~31,但是你需要考虑你月的天数) 月(0~11) 天(星期)(1~7 1=SUN 或 SUN,MON,TUE,WED,THU,FRI,SAT) 年份(1970-2099)


好了,根据上面的解释, 如果要生成一个每个星期一中午12点的定时任务,cron表达式该怎么写呢?答案如下:


0 0 12 ? * 2


最后一个2代表的是周一MON的意思,也就是说星期域的设置是从星期天SUN(即数字1)开始的。但是,如果你将这个表达式放入到Spring的@Scheduled注解中,你会发现,定时器并没有在星期一的中午执行任务,而是在下一天也就是每个月的星期二中午12点执行任务。


也就是说,在Spring中,执行定时任务时,星期这一字段如果是以数字表示的话,会比常规的cron表达式晚一天。


其实这种情况不光是在@Scheduled注解中,如果你在代码中直接使用Spring的CronTask类,传递cron表达式的时候同样会有这种问题。这到底是是为什么呢?


让我们从Spring源码着手来看看原因。查看CronTask类的源码,我们最常使用的是如下构造器:


public CronTask(Runnable runnable, String expression) {    this(runnable, new CronTrigger(expression));  }


关键则在于生成CronTrigger,那么我们继续查看CronTrigger的源码,会看到这样的代码:


public CronTrigger(String expression) {  this.sequenceGenerator = new CronSequenceGenerator(expression);}


继续查看CronSequenceGenerator


public CronSequenceGenerator(String expression) {    this(expression, TimeZone.getDefault());  }


这里用到了默认的时区,也就是你当前机器上的时区。继续查看this即构造方法:


public CronSequenceGenerator(String expression, TimeZone timeZone) {    this.expression = expression;    this.timeZone = timeZone;    parse(expression);  }


关键来了,parse就是用于转换传入的cron表达式的,继续跟踪:


/**   * Parse the given pattern expression.   */  private void parse(String expression) throws IllegalArgumentException {    String[] fields = StringUtils.tokenizeToStringArray(expression, " ");    if (!areValidCronFields(fields)) {      throw new IllegalArgumentException(String.format(          "Cron expression must consist of 6 fields (found %d in \"%s\")", fields.length, expression));    }    doParse(fields);  }


同样进入关键代码doParse(fields)


private void doParse(String[] fields) {    setNumberHits(this.seconds, fields[0], 0, 60);    setNumberHits(this.minutes, fields[1], 0, 60);    setNumberHits(this.hours, fields[2], 0, 24);    setDaysOfMonth(this.daysOfMonth, fields[3]);    setMonths(this.months, fields[4]);    setDays(this.daysOfWeek, replaceOrdinals(fields[5], "SUN,MON,TUE,WED,THU,FRI,SAT"), 8);
    if (this.daysOfWeek.get(7)) {      // Sunday can be represented as 0 or 7      this.daysOfWeek.set(0);      this.daysOfWeek.clear(7);    }  }


而setDay代码如下:


private void setDays(BitSet bits, String field, int max) {    if (field.contains("?")) {      field = "*";    }    setNumberHits(bits, field, 0, max);  }


从这两段代码便可以看出为什么Spring的星期用数字表达的话会不符合常规的cron表达式的端倪了。


对于星期,先将该域的英文缩写SUN-SAT替换成对应的数字(0-6),接着将该域中的字符"?"替换成"*",然后使用基础解析算法处理。最后,由于周日对应的值有两个0和7,因此对daysOfWeek位数组的第0位和第7位取或,将结果保存到第0位,并清除第7位。


也就是说SUM在这里其实代表的是0,而不是cron表达式中的1,其他星期也依次类推,也就是比常规的cron表达式小1,从代码中的注释也可以看出星期设置的不同:


// Sunday can be represented as 0 or 7this.daysOfWeek.set(0);


好吧,这个就是为什么我们将cron表达式用于Spring定时器的时候,用数字表示星期会出现问题的原因,而@Scheduled注解传入的cron表达式最后也是用到CronSequenceGenerator进行处理。


可是网上写Spring定时器的文章中,都是提cron表达式的解释,却很少能发现对这个问题的说明。强哥看到了许多文章,就只有一篇中有提到关于星期问题的描述,如下:


注:第六位(星期几)中的数字可能表达不太正确,可以使用英文缩写来表示,如:Sun


但是原因并没有说明出来。不过,也正如上面说提到的,既然数字容易产生混乱和错误,那么我们最好就是使用英文缩写进行处理cron表达式中的日期,强哥也亲自试验过,英文缩写的方式是不会有上述的少一天的问题的。


不清楚为什么Spring代码设计人员要以这种方式来处理定时器中的星期字段,极易引发错误。不过可能与Linux的计划任务Cron有关,因为在Crontab中,星期域也是从0~7,0和7均为星期天,同时Crontab中的cron表达式没有秒域,以五个或六个数字来表示。(看到这些各种不一的用法是不是心里千万只草拟马在崩腾)


所以,大家如果有遇到在Spring中使用cron表达式做定时任务的话,最好使用英文缩写来处理星期域,这样能够保证星期不会出错。

相关文章
|
27天前
|
Java 调度 Spring
Spring之定时任务基本使用篇
本文介绍了在Spring Boot项目中使用定时任务的基本方法。主要通过`@Scheduled`注解实现,需添加`@EnableScheduling`开启定时任务功能。文中详细解析了Cron表达式的语法及常见实例,如每秒、每天特定时间执行等。此外,还探讨了多个定时任务的执行方式(并行或串行)及其潜在问题,并留待后续深入讨论。
104 64
|
6月前
|
资源调度 Java 调度
Spring Cloud Alibaba 集成分布式定时任务调度功能
定时任务在企业应用中至关重要,常用于异步数据处理、自动化运维等场景。在单体应用中,利用Java的`java.util.Timer`或Spring的`@Scheduled`即可轻松实现。然而,进入微服务架构后,任务可能因多节点并发执行而重复。Spring Cloud Alibaba为此发布了Scheduling模块,提供轻量级、高可用的分布式定时任务解决方案,支持防重复执行、分片运行等功能,并可通过`spring-cloud-starter-alibaba-schedulerx`快速集成。用户可选择基于阿里云SchedulerX托管服务或采用本地开源方案(如ShedLock)
189 1
|
4月前
|
Java BI 调度
Java Spring的定时任务的配置和使用
遵循上述步骤,你就可以在Spring应用中轻松地配置和使用定时任务,满足各种定时处理需求。
202 1
|
4月前
|
存储 Java API
简单两步,Spring Boot 写死的定时任务也能动态设置:技术干货分享
【10月更文挑战第4天】在Spring Boot开发中,定时任务通常通过@Scheduled注解来实现,这种方式简单直接,但存在一个显著的限制:任务的执行时间或频率在编译时就已经确定,无法在运行时动态调整。然而,在实际工作中,我们往往需要根据业务需求或外部条件的变化来动态调整定时任务的执行计划。本文将分享一个简单两步的解决方案,让你的Spring Boot应用中的定时任务也能动态设置,从而满足更灵活的业务需求。
299 4
|
7月前
|
资源调度 Java 调度
Spring Cloud Alibaba 集成分布式定时任务调度功能
Spring Cloud Alibaba 发布了 Scheduling 任务调度模块 [#3732]提供了一套开源、轻量级、高可用的定时任务解决方案,帮助您快速开发微服务体系下的分布式定时任务。
15226 37
|
6月前
|
Java 开发者 Spring
Spring Boot实战宝典:揭秘定时任务的幕后英雄,让业务处理如流水般顺畅,轻松驾驭时间管理艺术!
【8月更文挑战第29天】在现代应用开发中,定时任务如数据备份、报告生成等至关重要。Spring Boot作为流行的Java框架,凭借其强大的集成能力和简洁的配置方式,为开发者提供了高效的定时任务解决方案。本文详细介绍了如何在Spring Boot项目中启用定时任务支持、编写定时任务方法,并通过实战案例展示了其在业务场景中的应用,同时提供了注意事项以确保任务的正确执行。
68 0
|
6月前
|
Dubbo Java 调度
揭秘!Spring Cloud Alibaba的超级力量——如何轻松驾驭分布式定时任务调度?
【8月更文挑战第20天】在现代微服务架构中,Spring Cloud Alibaba通过集成分布式定时任务调度功能解决了一致性和可靠性挑战。它利用TimerX实现任务的分布式编排与调度,并通过`@SchedulerLock`确保任务不被重复执行。示例代码展示了如何配置定时任务及其分布式锁,以实现每5秒仅由一个节点执行任务,适合构建高可用的微服务系统。
115 0
|
7月前
|
SQL Java 调度
实时计算 Flink版产品使用问题之使用Spring Boot启动Flink处理任务时,使用Spring Boot的@Scheduled注解进行定时任务调度,出现内存占用过高,该怎么办
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
|
7月前
|
XML Java Linux
Spring Task 定时任务没有定时执行是为什么?
Spring Task 定时任务没有定时执行是为什么?
104 2
|
7月前
|
缓存 监控 Java
通用快照方案问题之Spring Boot Admin的定义如何解决
通用快照方案问题之Spring Boot Admin的定义如何解决
82 0