SpringBoot 内置定时任务

146 阅读5分钟

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 运行效果

image.png

当你修改了任务执行周期后,生效时间为执行完最近一次任务后。这一点是需要注意的,用生活中的例子理解就是我们取消电话卡的套餐也要下个月生效,含义是一样的。