zoukankan      html  css  js  c++  java
  • spring定时任务的数字星期域不符合常规的cron定义

    大家都知道,使用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 7
    this.daysOfWeek.set(0);
    好吧,这个就是为什么我们将cron表达式用于Spring定时器的时候,用数字表示星期会出现问题的原因,而@Scheduled注解传入的cron表达式最后也是用到CronSequenceGenerator进行处理。

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


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

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

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

  • 相关阅读:
    java OA系统 自定义表单 流程审批 电子印章 手写文字识别 电子签名 即时通讯
    flowable 获取当前任务流程图片的输入流
    最新 接口api插件 Swagger3 更新配置详解
    springboot 集成 activiti 流程引擎
    java 在线考试系统源码 springboot 在线教育 视频直播功能 支持手机端
    阿里 Nacos 注册中心 配置启动说明
    springboot 集成外部tomcat war包部署方式
    java 监听 redis 过期事件
    springcloudalibaba 组件版本关系
    java WebSocket 即时通讯配置使用说明
  • 原文地址:https://www.cnblogs.com/hyhy904/p/11102191.html
Copyright © 2011-2022 走看看