杰瑞科技汇

Spring定时器如何实现与配置?

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

Spring定时器如何实现与配置?-图1
(图片来源网络,侵删)
  1. Spring 自带的 @Scheduled:最简单、最轻量级的方式。
  2. Spring Boot + @Scheduled:在 Spring Boot 项目中的标准配置。
  3. Spring Task (Quartz/SimpleScheduleBean) 集成:功能更强大,支持持久化、集群等。
  4. 分布式任务调度框架 (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定时器如何实现与配置?-图2
(图片来源网络,侵删)

在任何一个 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: 初始延迟,单位是毫秒,表示第一次任务执行前需要等待的毫秒数,通常与 fixedDelayfixedRate 一起使用。
  • 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 自动配置了所需的一切。

步骤:

  1. 添加依赖:如果你的 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>
  2. 配置线程池(推荐):默认情况下,Spring 使用一个单线程的 ScheduledExecutorService 来执行所有定时任务,如果你的任务很多或某个任务耗时很长,这可能会导致后续任务被阻塞。强烈建议自定义一个线程池

    Spring定时器如何实现与配置?-图3
    (图片来源网络,侵删)

    application.propertiesapplication.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));
        }
    }
  3. 编写定时任务:和上面完全一样,创建 @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 集群,共同分担任务负载,实现高可用。
  • 丰富的调度功能:支持更复杂的 CronTriggerSimpleTrigger
  • 事务管理:可以方便地与 Spring 的事务管理集成。

分布式任务调度框架 (XXL-JOB, Elastic-Job)

在微服务架构下,每个服务实例都可能有自己的定时任务,这会导致任务重复执行(每个订单服务实例都在同一时间执行清理过期订单的任务),为了解决这个问题,需要专门的分布式任务调度中心

核心思想:

  • 调度中心:一个独立的服务,负责统一管理和调度所有任务。
  • 执行器:部署在各个业务服务中,负责接收调度中心的指令并执行任务。
  • 注册发现:执行器需要向调度中心注册自己,调度中心知道有哪些执行器可用。
  • 路由策略:调度中心根据策略(如轮询、随机、故障转移等)选择一个执行器来执行任务。

主流框架对比:

特性 XXL-JOB Elastic-Job
调度中心 自建,一个独立的 Web 应用 无中心节点,基于 Zookeeper/Consul 协调
部署复杂度 较高,需要额外部署调度中心 较低,只需引入依赖,与业务服务一同部署
功能 功能非常丰富,如路由策略、阻塞处理、故障转移、监控告警等 功能相对基础,专注于分片和容错
社区/公司 个人/公司项目,国内流行度高 携程开源,成熟稳定
适用场景 中大型项目,需要强大功能和可视化管理 对中心化有顾虑,或需要更轻量级、高可用的方案

XXL-JOB 工作流程简介:

  1. 部署调度中心:下载 XXL-JOB 的源码,部署一个独立的 Web 应用。
  2. 配置执行器:在你的 Spring Boot 项目中引入 XXL-JOB 依赖,并配置执行器的AppName和地址,使其能被调度中心发现。
  3. 配置任务:在调度中心的 Web UI 上创建新任务,配置 Cron 表达式、执行器、路由策略等。
  4. 执行任务:调度中心根据配置,向选定的执行器发送 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 功能强大,支持持久化、集群、复杂调度 配置相对复杂,需要数据库支持 中大型单体应用,对任务可靠性要求高的场景
分布式调度框架 解决分布式环境下的任务重复执行,支持水平扩展、高可用 引入第三方系统,架构复杂,运维成本高 微服务架构,多个服务实例需要协同或独立执行同一任务的场景

如何选择?

  1. 新手或简单场景:直接使用 Spring Boot + @Scheduled,它足够简单且能满足大部分需求。
  2. 单体应用,对可靠性要求高:如果任务不能因重启而丢失,需要集群部署,选择 Spring + Quartz
  3. 微服务架构:这是必然选择。XXL-JOBElastic-Job 都是优秀的选择,如果你喜欢功能全面、可视化管理强的,选 XXL-JOB;如果你倾向于无中心化、更轻量级的方案,选 Elastic-Job
分享:
扫描分享到社交APP
上一篇
下一篇