在 Spring Boot 中实现定时任务主要依赖于@Scheduled
注解和 Spring 调度器。
基本概念
定时任务,简单来说就是在特定的时间点或按照一定的时间间隔自动执行的任务。在 Spring Boot 中,实现定时任务主要依赖于 Spring 框架提供的 @Scheduled 注解和 TaskScheduler 接口。@Scheduled 注解可以方便地将一个方法标记为定时执行的任务,而 TaskScheduler 则负责任务的调度和执行。
Cron是一个字符串,由7个字段组成,使用空格隔开 ,用于指定定时任务的执行时间。也称为七子表达式
字段 | 取值范围 | 说明 |
---|---|---|
秒 | 0-59 | */10 |
分 | 0-59 | |
时 | 0-23 | 6-16 |
日 | 1-31 | |
月 | 1-12 | |
星期 | 0-7 | 0和7都表示星期日 |
年 | 1970~2099 | 此项非必需,可以省略 |
使用以下特殊字符来指定执行时间:
星号(*):表示匹配该字段的所有可能值
问号(?):表示不关心该字段具体的值
斜线(/):表示指定一个间隔
逗号(,):表示列举多个值
连字符(-):表示指定一个范围
Cron表达式在线生成器:在线Cron表达式生成器
一、定时任务核心实现
1. 核心注解与配置
- @Scheduled:标记方法为定时任务,支持多种触发方式(Cron、固定速率 / 延迟等)。
- @EnableScheduling:在启动类中启用定时任务功能。
- 线程池配置:默认单线程,建议通过
ThreadPoolTaskScheduler
配置多线程。
@SpringBootApplication
@EnableScheduling // 启用定时任务
public class RuoYiApplication {
public static void main(String[] args) {
SpringApplication.run(RuoYiApplication.class, args);
}
}
@Component
public class AttendanceCheckTask {
@Autowired
private ISysDeptService sysDeptService;
// 每天18点10分执行(Cron表达式)
@Scheduled(cron = "0 10 18 * * ?")
public void checkMonthlyAttendance() {
// 任务逻辑
}
}
2. Cron 表达式详解
- 结构:
秒 分 时 日 月 周
(共 6 个字段)。 - 示例:
0 0 1 * * ?
:每天凌晨 1 点。0 30 8 ? * 1-5
:工作日早上 8:30。0 0 0 1 * ?
:每月 1 号 0 点。
3. 线程池配置
@Configuration
public class SchedulerConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar registrar) {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(10); // 线程池大小
scheduler.setThreadNamePrefix("Attendance-Scheduler-");
registrar.setTaskScheduler(scheduler);
}
}
二、用户代码实现分析
1. 核心业务逻辑
@Component
public class AttendanceCheckTask {
@Autowired
private IAttendanceRecordService attendanceRecordService;
@Autowired
private ISysNoticesService sysNoticesService;
@Autowired
private ISysUserService sysUserService;
@Autowired
private ISysDeptService sysDeptService;
@Scheduled(cron = "0 10 18 * * ?")
public void checkMonthlyAttendance() {
// 1. 获取上月考勤数据
List<Map<String, Object>> userList = attendanceRecordService.selectUsersWithNormalDaysLastMonth();
// 2. 遍历生成公告
for (Map<String, Object> user : userList) {
Long userId = (Long) user.get("userId");
Integer normalDays = ((Long) user.get("normalDays")).intValue();
// 3. 查询用户和部门信息
SysUser sysUser = sysUserService.selectUserById(userId);
if (sysUser == null) {
logger.warn("用户ID {} 不存在,跳过", userId);
continue;
}
String deptName = sysDeptService.selectDeptById(sysUser.getDeptId()).getDeptName();
String userName = sysUser.getNickName();
// 4. 生成公告内容
String noticeContent = String.format(
"🎉 %s 部门的 %s 同志,在上月以 **%d 天全勤** 表现优异!\n" +
"你的坚持是团队的榜样,继续加油!",
deptName, userName, normalDays
);
// 5. 保存公告
saveNotice(noticeContent);
}
}
private void saveNotice(String content) {
SysNotices notice = new SysNotices();
notice.setNoticeTitle("月度全勤标兵表彰");
notice.setNoticeType("2");
notice.setNoticeContent(content);
notice.setStatus("0");
notice.setCreateBy("system");
sysNoticesService.insertSysNotices(notice);
}
}
2. 关键技术点
- 部门名称获取:通过
ISysDeptService
查询部门信息。 - 用户昵称获取:通过
ISysUserService
查询用户昵称。 - 公告内容优化:使用部门名称和用户昵称替代用户 ID。
三、高级特性与优化
1. 动态调整 Cron 表达式
@Configuration
public class DynamicCronConfig implements SchedulingConfigurer {
private volatile String cronExpression = "0 10 18 * * ?";
@Override
public void configureTasks(ScheduledTaskRegistrar registrar) {
registrar.addTriggerTask(
this::checkMonthlyAttendance,
triggerContext -> {
CronTrigger trigger = new CronTrigger(cronExpression);
return trigger.nextExecutionTime(triggerContext);
}
);
}
// 通过API或配置中心修改cronExpression
public void updateCron(String newCron) {
this.cronExpression = newCron;
}
}
2. 异常处理与重试
@Component
public class AttendanceCheckTask {
private static final Logger logger = LoggerFactory.getLogger(AttendanceCheckTask.class);
@Scheduled(cron = "0 10 18 * * ?")
public void checkMonthlyAttendance() {
try {
// 核心逻辑
} catch (Exception e) {
logger.error("定时任务执行失败:", e);
// 重试逻辑(如调用Feign接口触发重试)
}
}
}
3. 分布式定时任务方案
- XXL-JOB:可视化管理界面,支持分布式调度
// 引入依赖
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>2.3.1</version>
</dependency>
// 配置执行器
@Component
public class XxlJobConfig {
@Bean
public XxlJobSpringExecutor xxlJobExecutor() {
XxlJobSpringExecutor executor = new XxlJobSpringExecutor();
executor.setAdminAddresses("http://xxl-job-admin:8080/xxl-job-admin");
executor.setAppname("ruoyi-attendance");
executor.setAddress("");
return executor;
}
}
// 定义任务
@XxlJob("attendanceCheckJob")
public ReturnT<String> execute(String param) {
// 任务逻辑
return ReturnT.SUCCESS;
}
四、最佳实践与注意事项
- 线程安全:避免在任务中修改共享状态,必要时使用
@Async
异步处理。 - 数据库优化:
- 为考勤查询添加索引:
ALTER TABLE attendance_record ADD INDEX idx_month (user_id, year, month);
- 分页查询:
selectUsersWithNormalDaysLastMonth
方法添加分页参数。
- 为考勤查询添加索引:
- 监控与日志:
- 集成 Spring Boot Actuator:
management.endpoints.web.exposure.include=health,info,scheduledtasks
- 日志记录:在任务开始和结束时记录关键信息。
- 集成 Spring Boot Actuator:
- 性能调优:
- 使用缓存:将部门和用户信息缓存到 Redis。
- 异步处理:将公告生成和保存逻辑异步化。
五、总结
Spring Boot 的定时任务实现简单高效,通过@Scheduled
注解和线程池配置即可满足大部分需求。对于复杂场景(如分布式调度、动态配置),可结合 XXL-JOB 等框架扩展。在实际开发中,需注意线程安全、数据库性能和异常处理,确保任务稳定可靠运行。