[075][示例]基于自定义的定时任务框架实战详解
本文章代码: gitee , gitcode , github
1. 引言
在企业级应用中,定时任务(Scheduled Tasks)是不可或缺的基础设施,用于数据同步、报表生成、系统监控等场景。tutorials4j 是一个功能丰富的 Java 框架,其 Schedule 模块 在 Spring Boot 原生调度能力之上,提供了更强大的任务管理、状态监控、异常处理和配置灵活性。
本文通过一个完整的示例项目(examples-schedule),深入分析其核心功能、配置方式、任务实现、运行时状态处理以及日志分析,帮助读者快速掌握该框架的使用方法和最佳实践。
2. 示例项目概览
该示例是一个 Spring Boot 应用,启动类为 ExampleScheduleApplication,激活的 profile 为 schedule。它定义了四个定时任务(其中两个共用同一个任务类),并通过 application-schedule.yml 进行集中配置。主要组件包括:
- 任务类:
Demo1Task、Demo2Task、Demo3Task,均实现TaskRunner接口。 - 状态处理器:
DemoTaskRuntimeDataHandler,实现TaskRuntimeDataHandler,监听任务生命周期事件。 - 配置文件:
application-schedule.yml,声明任务元数据、执行策略和 Spring 线程池参数。
3. 配置详解:灵活的任务声明
application-schedule.yml 是调度框架的核心配置文件,它采用分层结构,支持每个任务独立配置。
3.1 全局配置
tutorials4j:
schedule:
spring:
enabled: true
all-task-auto-start-on-boot: true # 应用启动时自动注册所有任务
tasks: # 任务列表
...
enabled:总开关,可一键禁用调度功能。all-task-auto-start-on-boot:默认为true,表示 Spring 容器启动后自动将配置的任务加载到调度器中。
3.2 单个任务配置详解
以 demo1Task 为例:
demo1Task:
enabled: true
cron: 0/5 * * * * ? # 每5秒执行一次
class-simple-name: demo1Task # 对应 Spring Bean 名称(首字母小写)
description: 测试任务1
metadata: # 自定义元数据,传递给任务参数
key1: value1
cron:标准的 Cron 表达式,支持秒级精度。class-simple-name:指容器中TaskRunner实现类的 Bean 名称,框架通过该名称查找并执行。metadata:键值对,会作为Map<String,String>参数传入run方法,方便任务获取自定义配置。
3.3 高级执行策略
框架提供了三种增强的控制参数:
initial-delay
首次执行前的延迟时间,单位支持 s(秒)、ms(毫秒)等。例如 demo2Task 配置了 initial-delay: 10s,表示启动后等待10秒才触发第一次执行。
max-execution-count
最大执行次数,达到后任务自动停止。demo2Task 设置为 3,意味着它只执行三次(无论成功或失败),之后不再触发。
max-failure-count
最大连续失败次数,达到后任务被停止(状态变为 STOPPED)。demo3Task 设置为 3,若连续失败三次,框架会自动禁用该任务,避免无意义的重试。
这些策略使得任务管理更加健壮,尤其适合需要限流或降级的场景。
4. 任务实现:模拟业务逻辑与异常
三个任务类都实现了 TaskRunner 接口,其 run(Map<String, String> params) 方法是执行入口。
4.1 Demo1Task:基础任务
@Slf4j
@Component
public class Demo1Task implements TaskRunner {
@Override
public void run(Map<String, String> params) {
log.info(">>> demo1, {}, {}, {}",
Thread.currentThread().getName(), params, System.currentTimeMillis());
// 随机休眠 0~3 秒,模拟耗时操作
long milli = ThreadLocalRandom.current().nextInt(3000);
TimeUnit.MILLISECONDS.sleep(milli);
}
}
- 打印当前线程名、传入参数和时间戳。
- 随机休眠,模拟不同的执行时长,便于观察并发调度情况。
4.2 Demo2Task:可抛出异常的任务
@Override
public void run(Map<String, String> params) {
// 随机休眠 0~4 秒
// ...
int num = ThreadLocalRandom.current().nextInt(100);
if (num >= 70) {
// 30% 概率抛出异常
throw new RuntimeException("任务异常");
}
}
- 以 30% 的概率主动抛出运行时异常,模拟业务失败。
- 该任务配置了
max-execution-count=3,因此即便失败,也只执行三次。
4.3 Demo3Task:带失败次数限制的任务
demo3Task:
cron: 0/6 * * * * ?
max-failure-count: 3
- 任务本身不主动抛异常(仅随机休眠),但在实际生产环境中,失败可能来自外部依赖。
- 若连续失败三次,框架会将其状态置为
STOPPED,停止后续调度。
5. 运行时状态监控:TaskRuntimeDataHandler
框架内置了一套状态事件机制,允许开发者通过实现 TaskRuntimeDataHandler 接口来感知任务的完整生命周期。
@Slf4j
@Component
public class DemoTaskRuntimeDataHandler implements TaskRuntimeDataHandler {
@Override
@Async // 异步处理,不阻塞调度线程
public void handle(TaskRuntimeData data) {
log.info("任务实时运行状态:{}", data.taskStatus());
}
}
- 每个任务在状态变更时(如
CREATED、STARTED、COMPLETED、EXCEPTION、STOPPED)都会触发该处理方法。 - 通过
@Async注解,状态处理与任务执行解耦,避免影响调度性能。
从运行日志中可以看到状态流转的完整轨迹:
任务实时运行状态:CREATED # 任务注册时
任务实时运行状态:STARTED # 任务开始执行
任务实时运行状态:COMPLETED # 正常结束
任务实时运行状态:EXCEPTION # 执行抛出异常
任务实时运行状态:STOPPED # 达到失败次数限制或执行次数限制时
这种设计非常便于集成告警、日志聚合或执行历史记录系统。
6. 日志分析:任务执行全景
我们从 log.txt 中截取关键片段,观察任务如何按调度计划执行。
6.1 启动与注册
2026-06-28 22:01:50.377 DEBUG - 添加定时任务, taskCode=demo3Task
2026-06-28 22:01:50.387 DEBUG - 添加定时任务, taskCode=demo1Task
2026-06-28 22:01:50.387 INFO - 任务实时运行状态:CREATED # demo1Task
2026-06-28 22:01:50.387 DEBUG - 添加定时任务, taskCode=demo1Task_1
2026-06-28 22:01:50.387 INFO - 任务实时运行状态:CREATED # demo1Task_1
2026-06-28 22:01:50.388 DEBUG - 添加定时任务, taskCode=demo2Task
2026-06-28 22:01:50.388 INFO - 任务实时运行状态:CREATED # demo2Task
2026-06-28 22:01:50.389 INFO - 任务实时运行状态:CREATED # demo3Task
所有任务在启动时被注册,并立即触发 CREATED 事件。
6.2 demo2Task 的异常与执行次数限制
22:02:01.009 INFO - demo2, example-scheduler-1, {}, 1782655321009 # 第一次执行
22:02:01.010 INFO - 任务实时运行状态:STARTED
22:02:04.719 INFO - 任务实时运行状态:EXCEPTION
22:02:04.719 ERROR - Unexpected error occurred in scheduled task
java.lang.RuntimeException: 任务异常
...
22:02:06.000 INFO - demo2, example-scheduler-4, {}, 1782655326000 # 第二次执行
22:02:07.115 INFO - 任务实时运行状态:EXCEPTION
...
22:02:09.001 INFO - demo2, example-scheduler-5, {}, 1782655329001 # 第三次执行
22:02:09.001 INFO - 任务实时运行状态:STARTED
22:02:11.935 INFO - 任务实时运行状态:COMPLETED # 第三次成功,但达到最大执行次数
22:02:11.935 INFO - 任务实时运行状态:STOPPED
- 第一次和第二次执行抛出异常,第三次执行成功,但由于
max-execution-count=3,任务在完成后立即被标记为STOPPED,后续不再调度。日志中之后也确实没有demo2Task的任何日志。
6.3 demo3Task 的失败计数与停止
虽然示例中 demo3Task 未主动抛出异常,但若其多次失败(比如模拟外部服务故障),一旦连续失败次数达到 max-failure-count=3,框架会自动将其停止,日志中会出现 STOPPED 状态。这为生产环境的自我保护提供了有效手段。
6.4 线程池与并发执行
spring:
task:
scheduling:
pool:
size: 5
thread-name-prefix: example-scheduler-
从日志可见,任务执行线程名为 example-scheduler-1 ~ example-scheduler-5,共 5 个线程。当多个任务的 Cron 触发时间重叠时,它们会被分配给不同的线程并发执行,互不影响。
7. 高级特性与扩展点
- 元数据传递:
metadata允许为同一任务类配置不同的参数,如demo1Task和demo1Task_1使用同一个类但传递不同键值对,实现配置复用。 - 异步状态处理:
DemoTaskRuntimeDataHandler使用@Async,可以结合自己的线程池,避免处理逻辑拖慢主调度。 - 可插拔的任务仓库:框架支持从 YAML、数据库或配置中心加载任务定义,示例中使用的是 YAML 方式。
- 与分布式调度集成:
tutorials4j还提供了 Redis、Redisson、PowerJob、XXL-JOB 等适配,方便从单机迁移到分布式环境。
8. 总结
通过分析 examples-schedule 示例,我们全面了解了 tutorials4j 调度框架的核心能力:
- 灵活的配置:通过 YAML 集中管理任务,支持 Cron、固定延迟、初始延迟、执行次数和失败次数限制。
- 任务实现简单:只需实现
TaskRunner接口,并注入为 Spring Bean。 - 完善的状态监控:
TaskRuntimeDataHandler可捕获所有状态变更,便于日志、告警和运维。 - 健壮的异常处理:内置失败计数和自动停止机制,防止故障任务反复消耗资源。
- 可扩展性强:与多种分布式调度组件无缝集成,满足从单体到微服务的演进需求。
该框架不仅降低了定时任务开发的门槛,更通过标准化的设计提升了系统的可观测性和稳定性。建议开发者在实际项目中借鉴其设计理念,构建更可靠的调度体系。
附录:示例代码中的关键配置和类均已整理如上,读者可结合自己的业务需求进行定制。如需深入了解源码,可查阅 tutorials4j 官方文档或 GitHub 仓库。