在Spring源码中,解析cron的源码位于CronExpression中,在创立定时工作的时候,调用了CornExpression.parse办法做解析

public CronTrigger(String expression, ZoneId zoneId) {    Assert.hasLength(expression, "Expression must not be empty");    Assert.notNull(zoneId, "ZoneId must not be null");    this.expression = CronExpression.parse(expression);    this.zoneId = zoneId;}

那当初就让咱们揭开解析cron表达式的神秘面纱

public static CronExpression parse(String expression) {    Assert.hasLength(expression, "Expression string must not be empty");    // 如果 expression 是注解模式,就将注解替换为上面的模式(见尾部)    expression = resolveMacros(expression);    // StringUtils.tokenizeToStringArray 与 split办法性能差不多    String[] fields = StringUtils.tokenizeToStringArray(expression, " ");    if (fields.length != 6) {        // cron表达式必须由六项组成        throw new IllegalArgumentException(String.format(            "Cron expression must consist of 6 fields (found %d in \"%s\")", fields.length, expression));    }    try {        CronField seconds = CronField.parseSeconds(fields[0]); // 第一项是秒        CronField minutes = CronField.parseMinutes(fields[1]); // 第二项是分        CronField hours = CronField.parseHours(fields[2]); // 第三项是时        CronField daysOfMonth = CronField.parseDaysOfMonth(fields[3]); // 第四项是日        CronField months = CronField.parseMonth(fields[4]); // 第五项是月        CronField daysOfWeek = CronField.parseDaysOfWeek(fields[5]); // 第六项是年        return new CronExpression(seconds, minutes, hours, daysOfMonth, months, daysOfWeek, expression);    }    catch (IllegalArgumentException ex) {        String msg = ex.getMessage() + " in cron expression \"" + expression + "\"";        throw new IllegalArgumentException(msg, ex);    }}// resolveMacros 函数private static String resolveMacros(String expression) {    expression = expression.trim();    for (int i = 0; i < MACROS.length; i = i + 2) {        if (MACROS[i].equalsIgnoreCase(expression)) {            return MACROS[i + 1];        }    }    return expression;}private static final String[] MACROS = new String[] {    "@yearly", "0 0 0 1 1 *",    "@annually", "0 0 0 1 1 *",    "@monthly", "0 0 0 1 * *",    "@weekly", "0 0 0 * * 0",    "@daily", "0 0 0 * * *",    "@midnight", "0 0 0 * * *",    "@hourly", "0 0 * * * *"};

当初,cron表达式的程序咱们就记住,必须是六项,程序是 秒,分,时,日,月,年或者用零碎中定义的MACROS来代替,六项两头用空格隔开。那么到底每一项是怎么解析和表白的呢?来看看CronField中的相干定义。

// 秒public static CronField parseSeconds(String value) {    return BitsCronField.parseSeconds(value);}// 这调用栈就跟套娃一样public static BitsCronField parseSeconds(String value) {    return parseField(value, Type.SECOND);}private static BitsCronField parseField(String value, Type type) {    Assert.hasLength(value, "Value must not be empty");    Assert.notNull(type, "Type must not be null");    try {        BitsCronField result = new BitsCronField(type);        // 将字符串依照逗号分隔,也就是,咱们在每一项外面都能够用逗号来隔断,代表不同的工夫        String[] fields = StringUtils.delimitedListToStringArray(value, ",");        for (String field : fields) {            int slashPos = field.indexOf('/');            // 判断工夫中有没有斜杠            if (slashPos == -1) {                // 如果没有,就解析并设置工夫范畴                ValueRange range = parseRange(field, type);                result.setBits(range);            }            else {                String rangeStr = value.substring(0, slashPos);                String deltaStr = value.substring(slashPos + 1);                // 依据斜杠前的内容解析并创立工夫范畴                ValueRange range = parseRange(rangeStr, type);                if (rangeStr.indexOf('-') == -1) {                    // 如果斜杠前的表达式不蕴含横杠,则将以后range的完结工夫设置为以后类型的最大值                    range = ValueRange.of(range.getMinimum(), type.range().getMaximum());                }                int delta = Integer.parseInt(deltaStr);                if (delta <= 0) {                    throw new IllegalArgumentException("Incrementer delta must be 1 or higher");                }                // 将delta带入进去设置工夫范畴                result.setBits(range, delta);            }        }        return result;    }    catch (DateTimeException | IllegalArgumentException ex) {        String msg = ex.getMessage() + " '" + value + "'";        throw new IllegalArgumentException(msg, ex);    }}// parseRangeprivate static ValueRange parseRange(String value, Type type) {    if (value.equals("*")) {        // 如果是*号,则间接返回该类型的range()        return type.range();    }    else {        int hyphenPos = value.indexOf('-');        if (hyphenPos == -1) {            int result = type.checkValidValue(Integer.parseInt(value));            // 如果没有横杠,那么时间段的开始和完结都是以后事件点            return ValueRange.of(result, result);        }        else {            // 如果有横杠,那么时间段的开始为横杠前数字,完结就是横杠后的数字            int min = Integer.parseInt(value.substring(0, hyphenPos));            int max = Integer.parseInt(value.substring(hyphenPos + 1));            min = type.checkValidValue(min); // 校验            max = type.checkValidValue(max); // 校验            return ValueRange.of(min, max);        }    }}// setBits 办法,BitsCronField 在实现的时候用一个长整型的bits来存储一个工夫位private void setBits(ValueRange range) {    // 如果没有delta    if (range.getMinimum() == range.getMaximum()) {        // 如果是一个工夫点,因为咱们的bits的默认值是0,所以这里的语义就是间接将bits的第range.getMinimum()位,置为1        setBit((int) range.getMinimum());    }    else {        // 如果是一个时间段,则将Mask左移range.getMinimum()位的值设置为minMask        // 将Mask无符号右移 - (range.getMaximum() + 1) 位        // private static final long MASK = 0xFFFFFFFFFFFFFFFFL;        // 这里整得很简单是为了防止右移溢出的问题,然而实质上也是在bits的 range.getMinimum() 和 range.getMaximum() 位,置为1        long minMask = MASK << range.getMinimum();        long maxMask = MASK >>> - (range.getMaximum() + 1);        this.bits |= (minMask & maxMask);    }}// 有斜杠的状况调用这个办法private void setBits(ValueRange range, int delta) {    if (delta == 1) {        // 如果有delta,且为1,则跟没有没区别        setBits(range);    }    else {        // 如果delta不为1,则依照delta为公差设置地位1        for (int i = (int) range.getMinimum(); i <= range.getMaximum(); i += delta) {            setBit(i);        }    }}// 获取以后bits与(1L << index) 按位或的后果,按位或就是 有一则一// 咱们晓得,根本类型都是有默认值的,long型的默认值是0// 例如,如果是一个工夫点,因为咱们的bits的默认值是0,所以这里的语义就是间接将bits的第range.getMinimum()地位为1private void setBit(int index) {    this.bits |= (1L << index);}

刚刚外面调用了type.range办法,依据调用栈,最终会来到ChronoField枚举中,也就是说,如果是星号,返回的就是以后解析类型的整个事件范畴。从这里咱们能够看出,星号代表所有以后解析类型的所有工夫,如果表达式中有横杠,那么就代表一个时间段,如果是一个纯数字,那么就代表那个工夫点。

public enum ChronoField implements TemporalField {    NANO_OF_SECOND("NanoOfSecond", NANOS, SECONDS, ValueRange.of(0, 999_999_999)),    NANO_OF_DAY("NanoOfDay", NANOS, DAYS, ValueRange.of(0, 86400L * 1000_000_000L - 1)),    MICRO_OF_SECOND("MicroOfSecond", MICROS, SECONDS, ValueRange.of(0, 999_999)),    MICRO_OF_DAY("MicroOfDay", MICROS, DAYS, ValueRange.of(0, 86400L * 1000_000L - 1)),    MILLI_OF_SECOND("MilliOfSecond", MILLIS, SECONDS, ValueRange.of(0, 999)),    MILLI_OF_DAY("MilliOfDay", MILLIS, DAYS, ValueRange.of(0, 86400L * 1000L - 1)),    SECOND_OF_MINUTE("SecondOfMinute", SECONDS, MINUTES, ValueRange.of(0, 59), "second"),    SECOND_OF_DAY("SecondOfDay", SECONDS, DAYS, ValueRange.of(0, 86400L - 1)),    MINUTE_OF_HOUR("MinuteOfHour", MINUTES, HOURS, ValueRange.of(0, 59), "minute"),    MINUTE_OF_DAY("MinuteOfDay", MINUTES, DAYS, ValueRange.of(0, (24 * 60) - 1)),    HOUR_OF_AMPM("HourOfAmPm", HOURS, HALF_DAYS, ValueRange.of(0, 11)),    CLOCK_HOUR_OF_AMPM("ClockHourOfAmPm", HOURS, HALF_DAYS, ValueRange.of(1, 12)),    HOUR_OF_DAY("HourOfDay", HOURS, DAYS, ValueRange.of(0, 23), "hour"),    CLOCK_HOUR_OF_DAY("ClockHourOfDay", HOURS, DAYS, ValueRange.of(1, 24)),    AMPM_OF_DAY("AmPmOfDay", HALF_DAYS, DAYS, ValueRange.of(0, 1), "dayperiod"),    DAY_OF_WEEK("DayOfWeek", DAYS, WEEKS, ValueRange.of(1, 7), "weekday"),    ALIGNED_DAY_OF_WEEK_IN_MONTH("AlignedDayOfWeekInMonth", DAYS, WEEKS, ValueRange.of(1, 7)),    ALIGNED_DAY_OF_WEEK_IN_YEAR("AlignedDayOfWeekInYear", DAYS, WEEKS, ValueRange.of(1, 7)),    DAY_OF_MONTH("DayOfMonth", DAYS, MONTHS, ValueRange.of(1, 28, 31), "day"),    DAY_OF_YEAR("DayOfYear", DAYS, YEARS, ValueRange.of(1, 365, 366)),    EPOCH_DAY("EpochDay", DAYS, FOREVER, ValueRange.of((long) (Year.MIN_VALUE * 365.25), (long) (Year.MAX_VALUE * 365.25))),    ALIGNED_WEEK_OF_MONTH("AlignedWeekOfMonth", WEEKS, MONTHS, ValueRange.of(1, 4, 5)),    ALIGNED_WEEK_OF_YEAR("AlignedWeekOfYear", WEEKS, YEARS, ValueRange.of(1, 53)),    MONTH_OF_YEAR("MonthOfYear", MONTHS, YEARS, ValueRange.of(1, 12), "month"),    PROLEPTIC_MONTH("ProlepticMonth", MONTHS, FOREVER, ValueRange.of(Year.MIN_VALUE * 12L, Year.MAX_VALUE * 12L + 11)),    YEAR_OF_ERA("YearOfEra", YEARS, FOREVER, ValueRange.of(1, Year.MAX_VALUE, Year.MAX_VALUE + 1)),    YEAR("Year", YEARS, FOREVER, ValueRange.of(Year.MIN_VALUE, Year.MAX_VALUE), "year"),    ERA("Era", ERAS, FOREVER, ValueRange.of(0, 1), "era"),    INSTANT_SECONDS("InstantSeconds", SECONDS, FOREVER, ValueRange.of(Long.MIN_VALUE, Long.MAX_VALUE)),    OFFSET_SECONDS("OffsetSeconds", SECONDS, FOREVER, ValueRange.of(-18 * 3600, 18 * 3600));    private final String name;    private final TemporalUnit baseUnit;    private final TemporalUnit rangeUnit;    private final ValueRange range;    private final String displayNameKey;    private ChronoField(String name, TemporalUnit baseUnit, TemporalUnit rangeUnit, ValueRange range) {        this.name = name;        this.baseUnit = baseUnit;        this.rangeUnit = rangeUnit;        this.range = range;        this.displayNameKey = null;    }    private ChronoField(String name, TemporalUnit baseUnit, TemporalUnit rangeUnit,            ValueRange range, String displayNameKey) {        this.name = name;        this.baseUnit = baseUnit;        this.rangeUnit = rangeUnit;        this.range = range;        this.displayNameKey = displayNameKey;    }   // ... ...    @Override    public ValueRange range() {        return range;    }    // ... ...}

得出规定

从下面的源码剖析,咱们能够总结出这样一套cron表达式解析规定

1、cron表达式能够由 秒 分 时 日 月 年 六局部注册,每个局部由空格隔开。零碎中定义了一组用@结尾的字符串来代替规范Cron表达式,不过个数无限

private static final String[] MACROS = new String[] {    "@yearly", "0 0 0 1 1 *",    "@annually", "0 0 0 1 1 *",    "@monthly", "0 0 0 1 * *",    "@weekly", "0 0 0 * * 0",    "@daily", "0 0 0 * * *",    "@midnight", "0 0 0 * * *",    "@hourly", "0 0 * * * *"};

例如:

@Scheduled(cron = "@yearly")public void test(){    logger.info("123");}

2、对于每一项,能够用逗号隔开,用来示意不同的工夫点

例如:

@Scheduled(cron = "1,2,3 0 0 * * *")public void test(){    logger.info("123");}

3、对于每一项,能够应用横杠隔开,用来示意时间段

例如:

@Scheduled(cron = "1,2-4,5 0 0 * * *")public void test(){    logger.info("123");}

4、对于每一项,能够应用斜杠+横杠的组合,示意在这段时间内,以斜杠后的值为公差的工夫点

例如:

@Scheduled(cron = "1,2-20/3,5 0 0 * * *")public void test(){    logger.info("123");}

5、对于每一项,应用星号示意以后工夫类型的整个范畴

例如:

@Scheduled(cron = "1,2-20/3,5 * * * * *")public void test(){    logger.info("123");}

炒鸡辣鸡原创文章,转载请注明起源