6【hutool】hutool-cron

2,779 阅读4分钟

该系列文章主要是对 hutool 工具类的介绍,详情可以参考

hutool.cn/docs/#/

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 表达式

clipboard.png

传统的 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)不香吗。