项目中,或多或少都会使用到定时器,定时执行某些特殊得功能。而在 SpringBoot 项目中,使用得定时器功能就是使用 @Scheduled 注解。

当然,定时器功能打开了,但是没有定时,这是不可行的。所以,在定时器使用中,使用 Cron 表达式作为定时器规则,在 @Scheduled 注解源码中可以看到,除了 cron 属性之外还有多个功能不一的属性。

Cron 表达式解释入口

这里主要简述的是 Cron 表达式,在版本 1.5.6 版本的 SpringBoot 工程中,解析 Cron 表达式的源码如下(源码地址:org.springframework.scheduling.support.CronTrigger):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void parse(String expression) throws IllegalArgumentException {
String[] fields = StringUtils.tokenizeToStringArray(expression, " ");
if (fields.length != 6) {
throw new IllegalArgumentException(String.format(
"Cron expression must consist of 6 fields (found %d in \"%s\")", fields.length, expression));
}
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);
}
}

从以上源码看出,当前版本的 SpringBoot 中,针对于 @ScheduledCron 表达式,只支持 6 位规则,而在网上有资料表示可以存在 7 位。

但是源码中,限制了只能有 6 位,否则抛出异常。

Cron 表达是对应的内容为: seconds minutes hours daysOfMonth months daysOfWeek

域的解释及源码

针对不同属性解释:

  • seconds:秒数;最小值为 0 ; 最大值为 60。
  • minutes:分钟;最小值为 0 ; 最大值为 60。
  • hours:小时;最小值为 0 ; 最大值为 23。
  • daysOfMonth:月中的某一天。
  • months:月份,这里使用的值为:FOO,JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC 其中一个,且必须全部大写。
  • daysOfWeek:一周中的某一天,这里使用的值为:SUN,MON,TUE,WED,THU,FRI,SAT 其中一个,且必须全部大写。

在解释这个规则时,都是用了相同的两个方法,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
private void setNumberHits(BitSet bits, String value, int min, int max) {
String[] fields = StringUtils.delimitedListToStringArray(value, ",");
for (String field : fields) {
if (!field.contains("/")) {
// Not an incrementer so it must be a range (possibly empty)
int[] range = getRange(field, min, max);
bits.set(range[0], range[1] + 1);
}
else {
String[] split = StringUtils.delimitedListToStringArray(field, "/");
if (split.length > 2) {
throw new IllegalArgumentException("Incrementer has more than two fields: '" +
field + "' in expression \"" + this.expression + "\"");
}
int[] range = getRange(split[0], min, max);
if (!split[0].contains("-")) {
range[1] = max - 1;
}
int delta = Integer.valueOf(split[1]);
if (delta <= 0) {
throw new IllegalArgumentException("Incrementer delta must be 1 or higher: '" +
field + "' in expression \"" + this.expression + "\"");
}
for (int i = range[0]; i <= range[1]; i += delta) {
bits.set(i);
}
}
}
}

private int[] getRange(String field, int min, int max) {
int[] result = new int[2];
if (field.contains("*")) {
result[0] = min;
result[1] = max - 1;
return result;
}
if (!field.contains("-")) {
result[0] = result[1] = Integer.valueOf(field);
}
else {
String[] split = StringUtils.delimitedListToStringArray(field, "-");
if (split.length > 2) {
throw new IllegalArgumentException("Range has more than two fields: '" +
field + "' in expression \"" + this.expression + "\"");
}
result[0] = Integer.valueOf(split[0]);
result[1] = Integer.valueOf(split[1]);
}
if (result[0] >= max || result[1] >= max) {
throw new IllegalArgumentException("Range exceeds maximum (" + max + "): '" +
field + "' in expression \"" + this.expression + "\"");
}
if (result[0] < min || result[1] < min) {
throw new IllegalArgumentException("Range less than minimum (" + min + "): '" +
field + "' in expression \"" + this.expression + "\"");
}
return result;
}

特殊字符解释及例子

每个域都使用数字,或者使用连接符连接,含义:

  • , :连接符,表示列出枚举值。如在 minutes 域使用 2,15 ,表示 2 分和 15 分执行一次。
  • - :连接符,表示范围。如在 minutes 域使用 2-15 ,表示从 2 分到 15 分,每分钟执行一次。
  • / :连接符,表示值增加的幅度,如 n/m ,表示从第 n 秒开始,每隔 m 秒执执行一次,5/15 –>> 5, 20, 35, 50
  • * :表示匹配该域的任意值,如在 minutes 域使用,表示每分钟都会触发一次
  • ? :表示匹配该域的任意值,但只有 daysOfMonth 和 daysOfWeek 域才能使用,因为 daysOfMonth 和 daysOfWeek 域会相互影响,所以两个域不能同时使用 ? 。

注:在源码中未使用到的特殊符号有 L (表最后)、 W (表有效工作日)、 LW (表最后某月最后一个工作日)、 # (表用于确定每个月第几个星期几)

一些常用的例子,这里用表格展示:

规则 解释
0 0 10,14,16 * * ? 每天上午10点,下午2点,4点
0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时
0 0 12 ? * WED 表示每个星期三中午12点
0 0 12 * * ? 每天中午12点触发
0 15 10 ? * * 每天上午10:15触发
0 15 10 * * ? 每天上午10:15触发
0 * 14 * * ? 在每天下午2点到下午2:59期间的每1分钟触发
0 0/5 14 * * ? 在每天下午2点到下午2:55期间的每5分钟触发
0 0/5 14,18 * * ? 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
0 0-5 14 * * ? 在每天下午2点到下午2:05期间的每1分钟触发
0 10,44 14 ? 3 WED 每年三月的星期三的下午2:10和2:44触发
0 15 10 ? * MON-FRI 周一至周五的上午10:15触发
0 15 10 15 * ? 每月15日上午10:15触发

总结

文章所述与网上资料有所不同,需要根据源码判断,是否正确使用。
根据目前 SpringBoot 版本 1.5.6 来看,对于 Cron 表达式的使用相对简单。

参考资料

  1. spring cron表达式(定时器)
  2. Spring 定时器的时间设置规则

最后更新: 2020年04月07日 22:29

原始链接: https://maiyikai.github.io/2019/12/27/1577427502/

× ~谢谢大爷~
打赏二维码