Spring Boot 定时任务(Task)使用教程:从入门到精通

在日常开发中,定时任务是一个非常常见的需求,比如定时发送短信、定时生成报表、定时清理缓存等。Spring Boot 内置了 spring-boot-starter 模块,其中就包含了对定时任务的支持,无需额外引入复杂的第三方框架(如 Quartz),就能快速实现定时任务功能。本文将从基础到进阶,详细讲解 Spring Boot 定时任务的使用方法。

一、Spring Boot 定时任务基础

1.1 什么是定时任务?

定时任务是指在预定的时间点或按照固定的时间间隔自动执行的任务。在 Java 生态中,常见的定时任务实现方式有 TimerScheduledExecutorService、Quartz 等,而 Spring Boot 提供的 @Scheduled 注解则是对这些底层实现的封装,简化了开发流程。

1.2 启用定时任务

Spring Boot 中启用定时任务非常简单,只需两步:

  1. 添加依赖:Spring Boot 的 spring-boot-starter 已经包含了定时任务相关的依赖,无需额外引入。如果是 Maven 项目,pom.xml 中默认的 spring-boot-starter 即可满足需求:
1
2
3
4
5
6

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>

  1. 开启定时任务注解:在 Spring Boot 启动类上添加 @EnableScheduling 注解,用于启用定时任务的自动配置:
1
2
3
4
5
6
7
8
9
10
11
12
13

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling // 开启定时任务
public class TaskDemoApplication {
public static void main(String[] args) {
SpringApplication.run(TaskDemoApplication.class, args);
}
}

二、简单定时任务实现

启用定时任务后,只需在需要定时执行的方法上添加 @Scheduled 注解,并指定任务的执行时机即可。@Scheduled 注解支持多种定时方式,下面介绍最常用的几种。

2.1 固定延迟执行(fixedDelay)

fixedDelay 表示上一次任务执行完成后,间隔固定时间再执行下一次任务。单位为毫秒。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;

@Component // 交给 Spring 管理
public class SimpleTask {

// 上一次任务执行完成后,间隔 3 秒执行下一次
@Scheduled(fixedDelay = 3000)
public void fixedDelayTask() {
System.out.println("固定延迟任务执行时间:" + LocalDateTime.now());
// 模拟任务执行耗时
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

执行结果示例:两次任务的间隔时间为 3000ms + 任务执行耗时(1000ms),即约 4 秒。

2.2 固定频率执行(fixedRate)

fixedRate 表示按照固定的频率执行任务,无论上一次任务是否执行完成。单位为毫秒。

1
2
3
4
5
6
7
8
9
10
11
12

// 每隔 3 秒执行一次,不管上一次任务是否完成
@Scheduled(fixedRate = 3000)
public void fixedRateTask() {
System.out.println("固定频率任务执行时间:" + LocalDateTime.now());
// 模拟任务执行耗时
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

执行结果示例:两次任务的间隔时间约为 3 秒(因为任务耗时 1 秒,小于频率间隔)。如果任务耗时超过频率间隔(如任务耗时 4 秒),则上一次任务执行完成后会立即执行下一次任务。

2.3 初始延迟执行(initialDelay)

initialDelay 表示任务启动后,延迟指定时间再开始执行第一次任务,之后按照 fixedDelayfixedRate 执行。单位为毫秒。

1
2
3
4
5
6

// 启动后延迟 5 秒执行第一次任务,之后每隔 3 秒执行一次
@Scheduled(initialDelay = 5000, fixedRate = 3000)
public void initialDelayTask() {
System.out.println("初始延迟任务执行时间:" + LocalDateTime.now());
}

执行结果示例:应用启动 5 秒后,第一次执行任务,之后每 3 秒执行一次。

三、Cron 表达式:灵活的定时配置

如果需要更灵活的定时规则(如每天凌晨 2 点执行、每周一上午 10 点执行等),fixedDelayfixedRate 就无法满足需求了,此时需要使用 Cron 表达式

3.1 Cron 表达式格式

Cron 表达式是一个字符串,由 6 或 7 个字段组成,字段之间用空格分隔。Spring Boot 中的 Cron 表达式支持 6 个字段(不支持年份),格式如下:

秒 分 时 日 月 星期

每个字段的取值范围和允许的特殊字符如下:

字段取值范围特殊字符
0-59, - * /
0-59, - * /
0-23, - * /
1-31, - * / ? L W C
1-12 或 JAN-DEC, - * /
星期1-7 或 SUN-SAT(1 表示星期日), - * / ? L C #

3.2 常用特殊字符说明

  • *:表示所有可能的值,例如“秒”字段为 *,表示每秒执行。

  • ?:用于“日”和“星期”字段,表示不指定具体值,避免两者冲突。例如每月 10 日执行,不管是星期几,“星期”字段就填 ?

  • /:表示增量,例如“分”字段为 0/5,表示从 0 分开始,每 5 分钟执行一次。

  • -:表示范围,例如“时”字段为 9-17,表示 9 点到 17 点之间执行。

  • ,:表示多个值,例如“星期”字段为 1,3,5,表示星期日、星期二、星期四执行。

  • L:表示“最后”,例如“日”字段为 L,表示每月最后一天;“星期”字段为 L,表示星期六。

  • #:用于“星期”字段,表示每月的第几个星期几,例如 6#3 表示每月第三个星期五(6 表示星期五)。

3.3 Cron 表达式示例

  • 每秒执行:* * * * * ?

  • 每 5 分钟执行:0 */5 * * * ?

  • 每天凌晨 2 点执行:0 0 2 * * ?

  • 每月 10 日上午 10 点执行:0 0 10 10 * ?

  • 每周一到周五下午 5 点执行:0 0 17 ? * 2-6(2 表示星期一,6 表示星期五)

  • 每月最后一天晚上 11 点执行:0 0 23 L * ?

3.4 在 @Scheduled 中使用 Cron 表达式

只需将 @Scheduled 注解的 cron 属性设置为对应的表达式即可:

1
2
3
4
5
6

// 每天凌晨 2 点执行
@Scheduled(cron = "0 0 2 * * ?")
public void cronTask() {
System.out.println("Cron 任务执行时间:" + LocalDateTime.now());
}

四、定时任务参数外部化配置

在实际开发中,定时任务的执行规则可能需要根据环境动态调整(如开发环境和生产环境的执行频率不同)。此时,不建议将参数硬编码在 @Scheduled 注解中,而是通过 application.propertiesapplication.yml 进行外部化配置。

4.1 使用 properties 文件配置

application.properties 中添加定时任务参数:

1
2
3
4

# 定时任务参数配置
task.fixedRate=3000
task.cron=0 0 2 * * ?

在代码中通过 ${} 引用配置:

1
2
3
4
5
6
7
8
9
10
11
12

// 引用外部配置的 fixedRate
@Scheduled(fixedRateString = "${task.fixedRate}")
public void externalFixedRateTask() {
System.out.println("外部配置固定频率任务执行时间:" + LocalDateTime.now());
}

// 引用外部配置的 cron 表达式
@Scheduled(cron = "${task.cron}")
public void externalCronTask() {
System.out.println("外部配置 Cron 任务执行时间:" + LocalDateTime.now());
}

注意:对于数值类型的参数(如 fixedRatefixedDelay),需要使用 fixedRateStringfixedDelayString 属性来引用字符串类型的配置值。

五、定时任务的高级特性

5.1 定时任务线程池配置

Spring Boot 定时任务默认使用单线程执行,如果多个任务同时触发,会出现任务排队的情况。为了提高任务执行效率,可以配置定时任务线程池。

通过实现 SchedulingConfigurer 接口来配置线程池:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;

import java.util.concurrent.Executors;

@Configuration
public class ScheduledConfig implements SchedulingConfigurer {

@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
// 创建线程池,核心线程数为 5
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(5);
scheduler.setThreadNamePrefix("scheduled-task-"); // 线程名前缀
scheduler.initialize();

taskRegistrar.setTaskScheduler(scheduler);
}
}

配置后,定时任务会通过线程池中的线程并行执行,避免单线程瓶颈。

5.2 动态调整定时任务

如果需要在应用运行过程中动态修改定时任务的执行规则(如修改 Cron 表达式),可以通过 ScheduledTaskRegistrarTrigger 接口实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;

import java.time.LocalDateTime;

@Configuration
@EnableScheduling
public class DynamicTaskConfig implements SchedulingConfigurer {

// 从配置文件中读取 Cron 表达式,支持动态刷新
@Value("${task.dynamic.cron}")
private String cronExpression;

@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
// 动态注册定时任务
taskRegistrar.addTriggerTask(
() -> System.out.println("动态定时任务执行时间:" + LocalDateTime.now()),
triggerContext -> {
// 每次执行前重新读取 Cron 表达式,实现动态调整
CronTrigger trigger = new CronTrigger(cronExpression);
return trigger.nextExecutionTime(triggerContext);
}
);
}

// 提供方法修改 cronExpression
public void setCronExpression(String cronExpression) {
this.cronExpression = cronExpression;
}
}

通过调用 setCronExpression 方法,可以在应用运行时修改任务的执行规则。如果结合配置中心(如 Nacos、Apollo),还能实现配置的动态推送。

六、注意事项

  • 任务执行时长:如果任务执行时长超过了定时频率,会导致任务堆积。建议通过线程池配置或优化任务逻辑来避免。

  • 异常处理:定时任务方法中如果抛出未捕获的异常,会导致任务终止。建议在方法内部添加 try-catch 块,或通过全局异常处理器捕获异常。

  • 分布式环境:Spring Boot 内置的定时任务不支持分布式锁,在分布式环境下可能出现多个节点同时执行任务的情况。此时需要结合分布式锁(如 Redis 分布式锁)来保证任务的唯一性。

  • 幂等性:即使使用了分布式锁,也建议保证任务的幂等性,避免因网络抖动等问题导致任务重复执行。

七、总结

Spring Boot 定时任务通过 @EnableScheduling@Scheduled 注解,极大地简化了定时任务的开发。本文从基础的固定延迟、固定频率任务,到灵活的 Cron 表达式,再到参数外部化和线程池配置,最后介绍了动态任务和注意事项,覆盖了定时任务的大部分使用场景。

在实际开发中,需要根据业务需求选择合适的定时方式,并注意异常处理、分布式环境下的任务唯一性等问题。希望本文能帮助你快速掌握 Spring Boot 定时任务的使用😊