Spring Boot 提供了内置的 @Scheduled 注解,可以非常方便地实现简单的定时任务。
这种方式适合轻量级的定时任务,且不需要复杂的调度逻辑。
一、注解式定时任务
在 Spring Boot 项目中,@Scheduled 注解是内置支持的,因此不需要额外添加依赖。
1.1 启用定时任务支持
在主类或配置类上添加 @EnableScheduling 注解,启用定时任务支持。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
public class MySpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(MySpringBootApplication.class, args);
}
}
1.2 定义定时任务
在任意的 @Component 或 @Service 类中,使用 @Scheduled 注解定义定时任务,传入 Cron 表达式定义执行规则。
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class MyScheduledTasks {
// 每两小时运行一次
@Scheduled(cron = "0 0 0/2 * * ?")
public void runTask() {
System.out.println("任务执行了!当前时间:" + new java.util.Date());
}
}
fixedRate: 固定速率:每3秒执行一次,不管任务执行要多久
@Scheduled(fixedRate = 3000)
public void taskWithFixedRate() {
log.info("固定速率任务开始");
// 任务逻辑
}
fixedDelay: 固定延迟:上次执行完成后等待3秒再执行
@Scheduled(fixedDelay = 3000)
public void taskWithFixedDelay() {
log.info("固定延迟任务开始");
// 任务逻辑
}
initialDelay: 首次延迟5秒,之后每3秒执行一次,通常组合使用
@Scheduled(initialDelay = 5000, fixedRate = 3000)
public void taskWithInitialDelay() {
log.info("首次延迟任务开始");
// 任务逻辑
}
Cron表达式:每分钟的第0秒执行一次
@Scheduled(cron = "0 * * * * ?")
public void taskWithCron() {
log.info("Cron任务开始");
// 任务逻辑
}
1.3常见的 Cron 表达式
0 0/30 * * * ?:每30分钟执行一次。0 0 9-17 * * ?:每天的9点到17点之间,每小时执行一次。0 0 8,14 * * ?:每天的8点和14点各执行一次。0 0-5 14 * * ?:在每天14点的第0分钟至第5分钟的每分钟执行一次。0 0-5 14,18 * * ?:在每天14点和18点的第0分钟至第5分钟的每分钟执行一次。
/**
* 注解式定时任务示例(支持多种周期配置)
*/
@Slf4j
@Component
@EnableAsync // 开启异步任务
public class AnnotationScheduledTask {
// 固定速率:每3秒执行一次,不管任务执行要多久
@Async // 异步执行
@Scheduled(fixedRate = 3000)
public void taskWithFixedRate() {
log.info("固定速率任务开始");
// 任务逻辑
}
// 固定延迟:上次执行完成后等待3秒再执行
@Async // 异步执行
@Scheduled(fixedDelay = 3000)
public void taskWithFixedDelay() {
log.info("固定延迟任务开始");
// 任务逻辑
}
// 组合使用:首次延迟5秒,之后每3秒执行一次
@Async // 异步执行
@Scheduled(initialDelay = 5000, fixedRate = 3000)
public void taskWithInitialDelay() {
log.info("首次延迟任务开始");
// 任务逻辑
}
// Cron表达式:每分钟的第0秒执行一次
@Async // 异步执行
@Scheduled(cron = "0 * * * * ?")
public void taskWithCron() {
log.info("Cron任务开始");
// 任务逻辑
}
}
我常常也记不住,通常是在线生成的: Cron 表达式在线生成
1.4 异步线程池的配置
默认是单线程,可能会造成线程阻塞,提供了线程池的方式。
// @Scheduled 的线程池
@Configuration
public class SchedulingConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
// 创建一个线程池调度器
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
// 设置线程池大小
taskScheduler.setPoolSize(10);
// 设置线程名前缀,主要是为了在项目能够更快速的定位错误。
taskScheduler.setThreadNamePrefix("scheduled-task-pool-");
// 设置等待任务完成再关闭线程池
taskScheduler.setWaitForTasksToCompleteOnShutdown(true);
// 等待时间(单位:秒)
taskScheduler.setAwaitTerminationSeconds(60);
taskScheduler.initialize();
taskRegistrar.setTaskScheduler(taskScheduler);
}
}
1.5 异步调用
通过异步的方式解决阻塞线程问题。
在启动类或者配置类上加 @EnableAsync 注解
@EnableAsync // 开启异步支持
@EnableScheduling // 开启定时任务支持
@SpringBootApplication
public class TaskApplication {
public static void main(String[] args) {
SpringApplication.run(TaskApplication.class, args);
}
}
在方法上使用 @Async 注解
// 固定速率:每3秒执行一次,不管任务执行要多久
@Async // 异步执行
@Scheduled(fixedRate = 3000)
public void taskWithFixedRate() {
log.info("固定速率任务开始");
// 任务逻辑
}
二、动态定时任务
2.1 代码实现
第一步:建个数据库表。
CREATE TABLE `tb_cron` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '动态定时任务时间表',
`cron_expression` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '定时任务表达式',
`cron_describe` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
INSERT INTO `tb_cron` VALUES (1, '0 0/1 * * * ?', '每分钟执行一次');
第二步:导入数据库相关依赖,做到能从数据库查询数据。大家都会。🤸♂️
第三步:编写实体类和mapper
@Data
@TableName("tb_cron")
public class Cron {
private Long id;
private String cronExpression;
private String cronDescribe;
}
@Repository
public interface CronMapper extends BaseMapper<Cron> {
@Select("select cron_expression from tb_cron where id=1")
String getCron1();
}
第四步:写一个类 实现 SchedulingConfigurer🍻
实现 void configureTasks(ScheduledTaskRegistrar taskRegistrar); 方法,此方法的作用就是根据给定的 ScheduledTaskRegistrar 注册 TaskScheduler 和特定的Task实例
@Component
public class CompleteScheduleConfig implements SchedulingConfigurer {
@Autowired
@SuppressWarnings("all")
CronMapper cronMapper;
/**
* 执行定时任务.
*/
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.addTriggerTask(
//1.添加任务内容(Runnable)
() -> System.out.println("执行动态定时任务1: " + LocalDateTime.now().toLocalTime()+",此任务执行周期由数据库中的cron表达式决定"),
//2.设置执行周期(Trigger)
triggerContext -> {
//2.1 从数据库获取执行周期
String cron = cronMapper.getCron1();
//2.2 合法性校验.
if (cron!=null) {
// Omitted Code ..
}
//2.3 返回执行周期(Date)
return new CronTrigger(cron).nextExecutionTime(triggerContext);
}
);
}
}
2.2 运行效果
当你修改了任务执行周期后,生效时间为执行完最近一次任务后。这一点是需要注意的,用生活中的例子理解就是我们取消电话卡的套餐也要下个月生效,含义是一样的。