该系列文章主要是对 hutool 工具类的介绍,详情可以参考
Hutool的定时任务模块与Linux的Crontab使用上非常类似,通过一个cron.setting配置文件,简单调用start()方法即可简单使用。
同时还提供了秒匹配和年匹配等Quartz才有的功能,定时任务表达式上也同时兼容Crontab(Cron4j)和Quartz的表达式。
1 简单例子
使用 CronUtil 启动一个简单的定时任务
@Test
public void cron() {
CronUtil.schedule("*/2 * * * * *", (Task) () -> Console.log("hello world"));
// 支持秒级别定时任务
CronUtil.setMatchSecond(true);
CronUtil.start();
ThreadUtil.waitForDie();
}
2 守护线程
- 守护线程,守护主线程,会随着主线程结束而结束
- 非守护线程,用户线程,不会随主线程结束而结束
@Test
public void daemon() {
// 测试守护线程是否对作业线程有效
CronUtil.schedule("*/2 * * * * *", new InvokeTask("com.bruce.hutool.demo.cron.TestTask.doWhileTest"));
CronUtil.setMatchSecond(true);
// 当为守护线程时,stop方法调用后doWhileTest里的循环输出将终止,表示作业线程正常结束
// 当非守护线程时,stop方法调用后,不再产生新的作业,原作业正常执行。
CronUtil.start(true);
ThreadUtil.sleep(3000);
CronUtil.stop();
}
/**
* 执行循环定时任务,测试在定时任务结束时作为daemon线程是否能正常结束
*/
@SuppressWarnings("InfiniteLoopStatement")
public void doWhileTest() {
String name = Thread.currentThread().getName();
while (true) {
Console.log("Job {} while running...", name);
ThreadUtil.sleep(2000);
}
}
3 任务监听器 listener
用来监听定时任务的情况,可以用来处理异常,处理完成后的回调
当然也完全可以放在 Task 任务中处理
@Test
public void listener() {
CronUtil.getScheduler().addListener(new TaskListener() {
@Override
public void onStart(TaskExecutor executor) {
Console.log("task:{} start", executor.getCronTask().getId());
}
@Override
public void onSucceeded(TaskExecutor executor) {
Console.log("task:{} success", executor.getCronTask().getId());
}
@Override
public void onFailed(TaskExecutor executor, Throwable exception) {
Console.error("task:{} failed", executor.getCronTask().getId());
}
});
CronUtil.schedule("*/2 * * * * *", (Task) () -> Console.log("hello world"));
// 支持秒级别定时任务
CronUtil.setMatchSecond(true);
CronUtil.start();
ThreadUtil.waitForDie();
}
4 cron 表达式
这是定时任务的重头戏,无论你用的是 hutool 的工具,还是 quartz,schedule,或是 linux 系统的定时任务,都逃不开 cron 表达式
传统的 cron 表达式主要有以上五部分组成,以空格分隔。
- 在以上各个字段中,还可以使用以下特殊字符:
-
- 星号(*):代表所有可能的值,例如month字段如果是星号,则表示在满足其它字段的制约条件后每月都执行该命令操作。
- 逗号(,):可以用逗号隔开的值指定一个列表范围,例如,“1,2,5,7,8,9”
- 中杠(-):可以用整数之间的中杠表示一个整数范围,例如“2-6”表示“2,3,4,5,6”
- 正斜线(/):可以用正斜线指定时间的间隔频率,例如“0-23/2”表示每两小时执行一次。同时正斜线可以和星号一起使用,例如*/10,如果用在minute字段,表示每十分钟执行一次。
一些主流的定时任务框架,则支持 7 位表达式,即在原来的基础上,加上两位:
第一位代表秒,范围 0-59
最后一位代表年
这也就是以下代码的含义:
CronUtil.setMatchSecond(true);
hutool 采用建造者模式,提供了一种 cron 表达式的生成方式:
@Test
public void cronBuilder() {
String cron = CronPatternBuilder.of()
.set(Part.SECOND, "*")
.setRange(Part.MINUTE, 1, 59)
.setValues(Part.HOUR, 6, 7, 8)
.build();
Assert.assertEquals("* 1-59 6,7,8 * * *", cron);
}
另外,还可以通过表达式,获取下次执行时间
@Test
public void cronMatch() {
// 测试每30秒执行
List<Date> matchedDates = CronPatternUtil.matchedDates("0/30 * * * * ?", DateUtil.parse("2022-10-15 14:33:22"), 5, true);
Assert.assertEquals(5, matchedDates.size());
Assert.assertEquals("2022-10-15 14:33:30", matchedDates.get(0).toString());
Assert.assertEquals("2022-10-15 14:34:00", matchedDates.get(1).toString());
Assert.assertEquals("2022-10-15 14:34:30", matchedDates.get(2).toString());
Assert.assertEquals("2022-10-15 14:35:00", matchedDates.get(3).toString());
Assert.assertEquals("2022-10-15 14:35:30", matchedDates.get(4).toString());
}
5 最后
参考源码,可以看到,最终定时任务还是通过 ThreadPoolExecutor 来实现的。
定时任务是存在内存中的,一旦重启,或者是分布式的场景下,容易造成任务丢失。
加上如今市面上分布式定时任务框架很多,像是 xxl-job,quartz。
因此小编这边不太建议使用 hutool 的定时任务工具类。
当然,如果想快速搭建一个简单的单应用平台,hutool 定时任务工具类不失为最佳之选。
另外,小编开设这个专栏的意图,不仅是为了介绍下 hutool 的具体功能用法,更重要的是想把 hutool 开发者的设计思想推荐给大家。奈何小编才疏学浅,可能无法领略到代码的精髓。但是我还是希望和大家共同进步,把它的精华运用到工作生产中,省出来的时间再多学(mo)点(hui)别(er)的(yu)不香吗。