我会按照以下顺序进行讲解,从简单到复杂,并附上优缺点和适用场景:

- Spring 自带的
@Scheduled:最简单、最轻量级的方式。 - Spring Boot +
@Scheduled:在 Spring Boot 项目中的标准配置。 - Spring Task (Quartz/SimpleScheduleBean) 集成:功能更强大,支持持久化、集群等。
- 分布式任务调度框架 (XXL-JOB, Elastic-Job):用于微服务架构下的分布式任务调度。
Spring 自带的 @Scheduled (最常用、最简单)
这是 Spring 3.0 版本引入的注解,非常易于使用,你只需要在方法上添加 @Scheduled 注解,并配置好 Spring 的任务调度器即可。
核心步骤:
a) 开启定时任务功能
在你的 Spring 配置类(通常是 @Configuration 注解的类)上添加 @EnableScheduling 注解。
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableScheduling // 开启定时任务
public class SchedulingConfig {
// 这个类可以为空,仅用于配置
}
b) 创建定时任务方法

在任何一个 Spring 管理的 Bean 中,创建一个方法并使用 @Scheduled 注解。
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class MyScheduledTasks {
// 1. 固定延迟执行:上一次执行完毕后,等待5秒再执行下一次
@Scheduled(fixedDelay = 5000)
public void taskWithFixedDelay() {
System.out.println("Fixed Delay Task - 当前时间: " + new Date());
try {
// 模拟任务执行耗时3秒
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 2. 固定频率执行:每隔5秒执行一次,无论上一次任务是否完成
@Scheduled(fixedRate = 5000)
public void taskWithFixedRate() {
System.out.println("Fixed Rate Task - 当前时间: " + new Date());
}
// 3. 初始延迟后执行:第一次延迟2秒后开始,然后每隔5秒执行一次
@Scheduled(initialDelay = 2000, fixedRate = 5000)
public void taskWithInitialDelay() {
System.out.println("Initial Delay Task - 当前时间: " + new Date());
}
// 4. 使用 cron 表达式:每天凌晨1点执行
// 格式: [秒] [分] [小时] [日] [月] [周] [年(可选)]
@Scheduled(cron = "0 0 1 * * ?")
public void taskWithCronExpression() {
System.out.println("Cron Task - 每天凌晨1点执行 - 当前时间: " + new Date());
}
}
@Scheduled 注解参数详解:
fixedDelay: 固定延迟,单位是毫秒,表示上一个任务执行完毕后,再等待这么多毫秒,再执行下一个任务。fixedRate: 固定频率,单位是毫秒,表示每隔这么多毫秒就执行一次,不管上一个任务是否执行完。initialDelay: 初始延迟,单位是毫秒,表示第一次任务执行前需要等待的毫秒数,通常与fixedDelay或fixedRate一起使用。cron: Cron 表达式,最灵活、最强大的方式,可以定义出各种复杂的调度规则。
Cron 表达式示例:
| 表达式 | 说明 |
|---|---|
"0 * * * * *" |
每分钟的第0秒执行(每分钟执行一次) |
"0 0 * * * *" |
每小时的0分0秒执行(每小时执行一次) |
"0 0 12 * * *" |
每天12点(中午)执行 |
"0 0 0 * * SUN" |
每周日的午夜执行 |
"0 0/30 9-17 * * MON-FRI" |
工作日的上午9点到下午5点,每30分钟执行一次 |
"0 0 0 1 * *" |
每月1号的午夜执行 |
Spring Boot 中的 @Scheduled
在 Spring Boot 项目中,使用 @Scheduled 非常简单,因为 Spring Boot 自动配置了所需的一切。
步骤:
-
添加依赖:如果你的 Spring Boot 项目是基于
spring-boot-starter-parent的,通常不需要额外添加依赖,因为spring-boot-starter已经包含了它,如果不是,请确保添加spring-context。<!-- 如果你没有使用 spring-boot-starter-parent,需要手动添加 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> </dependency> -
配置线程池(推荐):默认情况下,Spring 使用一个单线程的
ScheduledExecutorService来执行所有定时任务,如果你的任务很多或某个任务耗时很长,这可能会导致后续任务被阻塞。强烈建议自定义一个线程池。
(图片来源网络,侵删)在
application.properties或application.yml中配置:# application.properties # 配置一个线程池,核心大小为5,最大为10,队列容量为100 spring.task.scheduling.pool.size=5 spring.task.scheduling.thread-name-prefix=scheduled-task-
或者通过
@Configuration类来更精细地配置:import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.SchedulingConfigurer; import org.springframework.scheduling.config.ScheduledTaskRegistrar; import java.util.concurrent.Executors; @Configuration public class ScheduleConfig implements SchedulingConfigurer { @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { // 创建一个固定大小的线程池来执行定时任务 taskRegistrar.setScheduler(Executors.newScheduledThreadPool(5)); } } -
编写定时任务:和上面完全一样,创建
@Component和@Scheduled方法即可。
Spring Task 集成 (Quartz)
@Scheduled 虽然方便,但功能有限,它不支持任务的持久化(如果应用重启,所有未执行的任务都会丢失)、集群部署,如果你的应用需要这些高级功能,可以集成业界标准的调度框架 Quartz。
Spring 对 Quartz 进行了很好的封装,使用起来也比较简单。
核心步骤:
a) 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
b) 创建 Job (任务)
你的任务需要继承 QuartzJobBean 并实现 executeInternal 方法。
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.stereotype.Component;
@Component
public class MyQuartzJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
// 从 JobDataMap 中可以获取到传递的参数
String jobName = context.getJobDetail().getJobDataMap().getString("jobName");
System.out.println("Executing Quartz Job: " + jobName + " - Time: " + new Date());
// 你的业务逻辑
}
}
c) 配置和触发 Job
你可以通过代码或配置文件来定义 Job 和 Trigger(触发器),这里展示最常用的 Java 配置方式。
import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class QuartzConfig {
// 1. 定义 JobDetail
@Bean
public JobDetail myJobDetail() {
// 关联我们自己的 Job 类
// 可以设置一些参数,存入 JobDataMap
return JobBuilder.newJob(MyQuartzJob.class)
.withIdentity("myJob", "group1") // 定义 Job 的名称和组
.usingJobData("jobName", "这是一个 Quartz Job") // 传递参数
.storeDurably() // 即使没有关联的 Trigger 也保留 JobDetail
.build();
}
// 2. 定义 Trigger (触发器)
@Bean
public Trigger myJobTrigger() {
// 简单的触发器:每5秒执行一次
SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(5)
.repeatForever();
return TriggerBuilder.newTrigger()
.forJob(myJobDetail()) // 关联 JobDetail
.withIdentity("myTrigger", "group1") // 定义 Trigger 的名称和组
.withSchedule(scheduleBuilder)
.build();
}
}
Quartz 的优势:
- 持久化:可以将 Job 和 Trigger 的状态保存到数据库中,即使应用重启,任务也能从上次中断的地方继续执行。
- 集群:多个节点可以组成一个 Quartz 集群,共同分担任务负载,实现高可用。
- 丰富的调度功能:支持更复杂的
CronTrigger和SimpleTrigger。 - 事务管理:可以方便地与 Spring 的事务管理集成。
分布式任务调度框架 (XXL-JOB, Elastic-Job)
在微服务架构下,每个服务实例都可能有自己的定时任务,这会导致任务重复执行(每个订单服务实例都在同一时间执行清理过期订单的任务),为了解决这个问题,需要专门的分布式任务调度中心。
核心思想:
- 调度中心:一个独立的服务,负责统一管理和调度所有任务。
- 执行器:部署在各个业务服务中,负责接收调度中心的指令并执行任务。
- 注册发现:执行器需要向调度中心注册自己,调度中心知道有哪些执行器可用。
- 路由策略:调度中心根据策略(如轮询、随机、故障转移等)选择一个执行器来执行任务。
主流框架对比:
| 特性 | XXL-JOB | Elastic-Job |
|---|---|---|
| 调度中心 | 自建,一个独立的 Web 应用 | 无中心节点,基于 Zookeeper/Consul 协调 |
| 部署复杂度 | 较高,需要额外部署调度中心 | 较低,只需引入依赖,与业务服务一同部署 |
| 功能 | 功能非常丰富,如路由策略、阻塞处理、故障转移、监控告警等 | 功能相对基础,专注于分片和容错 |
| 社区/公司 | 个人/公司项目,国内流行度高 | 携程开源,成熟稳定 |
| 适用场景 | 中大型项目,需要强大功能和可视化管理 | 对中心化有顾虑,或需要更轻量级、高可用的方案 |
XXL-JOB 工作流程简介:
- 部署调度中心:下载 XXL-JOB 的源码,部署一个独立的 Web 应用。
- 配置执行器:在你的 Spring Boot 项目中引入 XXL-JOB 依赖,并配置执行器的AppName和地址,使其能被调度中心发现。
- 配置任务:在调度中心的 Web UI 上创建新任务,配置 Cron 表达式、执行器、路由策略等。
- 执行任务:调度中心根据配置,向选定的执行器发送 HTTP 请求,执行你指定的 Bean 方法。
// XXL-JOB 在 Spring Boot 中的任务示例
@Component
public class XxlJobDemo {
@XxlJob("demoJobHandler") // "demoJobHandler" 必须与调度中心配置的任务处理器名称一致
public void demoJobHandler() throws Exception {
XxlJobHelper.log("XXL-JOB, Hello World.");
// 业务逻辑
System.out.println("执行 XXL-JOB 任务...");
}
}
总结与选型建议
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
@Scheduled |
简单、轻量、零配置 | 功能单一,不支持持久化、集群,单线程 | 个人项目、小型应用、非核心的简单定时任务(如日志清理、数据预热) |
| Spring + Quartz | 功能强大,支持持久化、集群、复杂调度 | 配置相对复杂,需要数据库支持 | 中大型单体应用,对任务可靠性要求高的场景 |
| 分布式调度框架 | 解决分布式环境下的任务重复执行,支持水平扩展、高可用 | 引入第三方系统,架构复杂,运维成本高 | 微服务架构,多个服务实例需要协同或独立执行同一任务的场景 |
如何选择?
- 新手或简单场景:直接使用 Spring Boot +
@Scheduled,它足够简单且能满足大部分需求。 - 单体应用,对可靠性要求高:如果任务不能因重启而丢失,需要集群部署,选择 Spring + Quartz。
- 微服务架构:这是必然选择。XXL-JOB 和 Elastic-Job 都是优秀的选择,如果你喜欢功能全面、可视化管理强的,选 XXL-JOB;如果你倾向于无中心化、更轻量级的方案,选 Elastic-Job。
