杰瑞科技汇

Spring定时任务如何配置与执行?

Spring 自带的 @Scheduled (最简单、最常用)

这是 Spring Framework 3.0 之后引入的注解,使用起来非常简单,无需引入额外的依赖,它基于 Java 自带的 java.util.concurrent.ScheduledExecutorService 实现。

Spring定时任务如何配置与执行?-图1
(图片来源网络,侵删)

核心步骤

第一步:添加 @EnableScheduling 注解

在你的 Spring Boot 应用的主类或者任何 @Configuration 配置类上,添加 @EnableScheduling 注解,用来开启对 @Scheduled 注解的支持。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling // 开启定时任务功能
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

第二步:创建任务类并注入 Spring 容器

创建一个普通的 Java 类,并在类上添加 @Component (或 @Service, @Repository 等) 注解,将其交给 Spring 管理。

Spring定时任务如何配置与执行?-图2
(图片来源网络,侵删)

第三步:在方法上添加 @Scheduled 注解

在类中的任何一个 public 方法上添加 @Scheduled 注解,并设置 cron 表达式或其他属性。

@Scheduled 注解详解

@Scheduled 注解主要有以下几个属性:

  • cron: 最强大的属性,用于定义复杂的执行时间规则,它接收一个 Cron 表达式
  • fixedDelay: 表示上一个任务执行完毕后,等待多久再执行下一个任务,单位是毫秒。@Scheduled(fixedDelay = 5000) 表示任务执行完后,等待5秒再执行。
  • fixedRate: 表示按照固定的频率执行任务,与上一个任务的开始时间点相关。@Scheduled(fixedRate = 5000) 表示每隔5秒执行一次,无论上一次任务是否执行完毕。
  • fixedDelayString / fixedRateString: 与上面两个类似,但值是字符串类型,可以配置在 .properties.yml 文件中,实现动态配置。

代码示例

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class MyScheduledTasks {
    private static final Logger log = LoggerFactory.getLogger(MyScheduledTasks.class);
    /**
     * fixedDelay: 上一个任务执行完毕后,等待5秒再执行。
     */
    @Scheduled(fixedDelay = 5000)
    public void taskWithFixedDelay() {
        log.info("执行 fixedDelay 任务,当前时间: {}", System.currentTimeMillis());
        try {
            // 模拟一个耗时3秒的任务
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    /**
     * fixedRate: 每隔5秒执行一次,不管上一次任务是否完成。
     * 如果任务执行时间超过了间隔时间,任务会重叠执行。
     */
    @Scheduled(fixedRate = 5000)
    public void taskWithFixedRate() {
        log.info("执行 fixedRate 任务,当前时间: {}", System.currentTimeMillis());
    }
    /**
     * cron: 使用 Cron 表达式,每天凌晨1点执行。
     */
    @Scheduled(cron = "0 0 1 * * ?")
    public void taskWithCron() {
        log.info("执行 Cron 任务,每天凌晨1点执行,当前时间: {}", System.currentTimeMillis());
    }
}

Cron 表达式详解

Cron 表达式是一个字符串,由6-7个时间字段组成,用来定义任务的执行时间。

Spring定时任务如何配置与执行?-图3
(图片来源网络,侵删)
字段 允许的值 特殊字符
0-59
0-59
0-23
日期 1-31 , - * / ? L W
月份 1-12JAN-DEC
星期 1-7SUN-SAT , - * / ? L #
年份 (可选) 1970-2099

特殊字符含义:

  • 匹配该字段的所有值。 在“分”字段上表示“每分钟”。
  • 只用于“日期”和“星期”字段,表示“不指定值”,这两个字段通常互斥,需要用 来避免冲突。
  • 表示一个范围。10-12 在“时”字段上表示“10点到12点”。
  • 表示一个列表。MON, WED, FRI 在“星期”字段上表示“周一、周三、周五”。
  • 表示一个增量。0/15 在“分”字段上表示“从0分钟开始,每15分钟一次”(即0, 15, 30, 45)。
  • L: "Last" 的缩写。
    • 在“日期”字段上,表示“该月的最后一天”。
    • 在“星期”字段上,表示“星期六”(7)或“最后指定的星期几”(如 5L 表示“最后一个星期五”)。
  • W: "Weekday" 的缩写,表示“最近的工作日”。15W 表示“如果15号是周六,则触发于14号(周五);如果是周日,则触发于16号(周一)”。
  • 表示“该月第几个星期几”。6#3 表示“该月第三个星期五”。

常用 Cron 表达式示例:

表达式 说明
0/5 * * * * ? 每5秒执行一次
0 0/10 * * * ? 每10分钟执行一次
0 0 0/1 * * ? 每小时执行一次
0 0 8 * * ? 每天8点执行
0 0 12 * * ? 每天12点(中午)执行
0 0 18 * * ? 每天18点(下午6点)执行
0 0 22 * * ? 每天22点(晚上10点)执行
0 0 0 1 * ? 每月1号凌晨0点执行
0 15 10 ? * MON-FRI 每周一至周五的上午10:15执行
0 15 10 L * ? 每月最后一天的上午10:15执行
0 15 10 ? * 6L 每月最后一个星期五的上午10:15执行
0 15 10 * * ? 2025 在2025年,每天的上午10:15执行

集成 Quartz (功能更强大、支持集群)

Quartz 是一个功能非常成熟和强大的开源作业调度库,当 @Scheduled 无法满足需求时(需要持久化任务、支持动态增删改任务、构建集群环境等),Quartz 是更好的选择。

核心概念

  • Job (作业): 定义需要执行的任务逻辑,是一个接口,只需实现 execute() 方法。
  • JobDetail (作业详情): 包含 Job 的实例以及调度所需的各种信息。
  • Trigger (触发器): 定义 Job 的执行时间规则,主要有两种:
    • SimpleTrigger: 简单触发器,用于在指定时间执行一次或重复执行多次。
    • CronTrigger: Cron 触发器,功能与 @Scheduled 的 cron 类似,但更强大。
  • Scheduler (调度器): 核心调度容器,负责将 Job 和 Trigger 绑定在一起并执行。

集成步骤

第一步:添加依赖

pom.xml 中添加 Spring Boot Starter for Quartz 的依赖。

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

第二步:创建 Job 类

创建一个类实现 org.quartz.Job 接口。

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyQuartzJob implements Job {
    private static final Logger log = LoggerFactory.getLogger(MyQuartzJob.class);
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        log.info("执行 Quartz Job,当前时间: {}", System.currentTimeMillis());
        // 在这里编写你的业务逻辑
    }
}

第三步:配置 Job 和 Trigger

在 Spring Boot 的配置类中,通过 @Bean 的方式来定义 JobDetail 和 Trigger,并将它们交给 Scheduler。

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() {
        // JobDetail 的绑定名是 "myJobDetail"
        // 不持久化,每次调度都会创建一个新的 Job 实例
        return JobBuilder.newJob(MyQuartzJob.class)
                .withIdentity("myJobDetail") // 给 JobDetail 一个唯一的标识
                .storeDurably() // 即使没有 Trigger 关联,也保留 JobDetail
                .build();
    }
    /**
     * 2. 定义 Trigger
     */
    @Bean
    public Trigger myJobTrigger() {
        // 简单触发器:立即执行,然后每隔5秒重复一次
        // SimpleScheduleBuilder simpleSchedule = SimpleScheduleBuilder.simpleSchedule()
        //         .withIntervalInSeconds(5)
        //         .repeatForever();
        // Cron 触发器:每10秒执行一次
        CronScheduleBuilder cronSchedule = CronScheduleBuilder.cronSchedule("0/10 * * * * ?");
        return TriggerBuilder.newTrigger()
                .forJob(myJobDetail()) // 关联上面定义的 JobDetail
                .withIdentity("myJobTrigger") // 给 Trigger 一个唯一的标识
                .withSchedule(cronSchedule) // 设置调度规则
                .build();
    }
}

Quartz 优势:

  • 持久化: 可以将 Job 和 Trigger 信息保存到数据库中,即使应用重启,任务也不会丢失。
  • 集群: 支持多节点集群部署,通过数据库锁机制保证任务不会重复执行。
  • 动态管理: 可以在运行时通过 API 动态地添加、删除、修改和暂停任务。

Elastic-Job (分布式任务调度)

如果你的应用是微服务架构,并且需要在多台服务器上协同执行定时任务(每台服务器只执行自己负责的任务,或者多台服务器共同完成一个大任务),Elastic-Job 是一个非常好的选择。

它由当当网开源,并已成为 Apache ShardingSphere 项目的一部分,是专门为分布式环境设计的任务调度解决方案。

核心特性

  • 分布式: 无中心化,协调通过 ZK 或 Etcd 完成。
  • 高可用: 任务在多个节点上运行,单个节点宕机不影响整体任务。
  • 分片: 将一个任务拆分成多个分片,每个节点执行不同的分片,实现任务并行处理。
  • 失效转移: 节点宕机后,其任务会自动转移到其他存活的节点上执行。
  • 限流: 支持设置每个分片的并发线程数,防止任务执行过载。

集成步骤 (以 Spring Boot + ZK 为例)

第一步:添加依赖

<dependency>
    <groupId>com.dangdang</groupId>
    <artifactId>elastic-job-lite-spring-boot-starter</artifactId>
    <version>3.0.1</version> <!-- 使用最新版本 -->
</dependency>
<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.8.1</version> <!-- 使用最新版本 -->
</dependency>
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>5.4.0</version> <!-- 使用最新版本 -->
</dependency>

第二步:配置 ZK 连接

application.yml 中配置 ZK 的连接信息。

elasticjob:
  reg-center:
    server-lists: localhost:2181
    namespace: elastic-job-demo
  dataflow:
    jdbc:
      datasource:
        url: jdbc:mysql://localhost:3306/elastic_job?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8
        username: root
        password: password

第三步:创建并配置 Job

import com.dangdang.ddframe.job.api.ShardingContext;
import com.dangdang.ddframe.job.api.dataflow.DataflowJob;
import com.dangdang.ddframe.job.config.JobCoreConfiguration;
import com.dangdang.ddframe.job.config.JobTypeConfiguration;
import com.dangdang.ddframe.job.lite.api.JobScheduler;
import com.dangdang.ddframe.job.lite.api.bootstrap.JobBootstrap;
import com.dangdang.ddframe.job.lite.config.LiteJobConfiguration;
import com.dangdang.ddframe.job.lite.spring.api.SpringJobScheduler;
import com.dangdang.ddframe.job.reg.zookeeper.ZookeeperRegistryCenter;
import com.dangdang.ddframe.job.util.Reflections;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.util.List;
@Configuration
public class ElasticJobConfig {
    @Autowired
    private ZookeeperRegistryCenter regCenter;
    @Autowired
    private DataSource dataSource;
    /**
     * 定义一个简单的定时任务
     */
    @Bean
    public SimpleJob mySimpleJob() {
        return new MySimpleJob();
    }
    @Bean
    public JobScheduler mySimpleJobScheduler(final SimpleJob simpleJob) {
        return new SpringJobScheduler(simpleJob, regCenter, getLiteJobConfiguration("mySimpleJob", "0/5 * * * * ?", 3, simpleJob.getClass()));
    }
    /**
     * 定义一个数据处理流任务
     */
    @Bean
    public DataflowJob<MyDataflowJob> myDataflowJob() {
        return new MyDataflowJob();
    }
    @Bean
    public JobScheduler myDataflowJobScheduler(final DataflowJob<MyDataflowJob> dataflowJob) {
        return new SpringJobScheduler(dataflowJob, regCenter, getLiteJobConfiguration("myDataflowJob", "0/10 * * * * ?", 3, dataflowJob.getClass()));
    }
    private LiteJobConfiguration getLiteJobConfiguration(String jobName, String cron, int shardingTotalCount, Class<? extends com.dangdang.ddframe.job.api.Job> jobClass) {
        JobCoreConfiguration coreConfig = JobCoreConfiguration.newBuilder(jobName, cron, shardingTotalCount)
                .shardingItemParameters("0=Beijing,1=Shanghai,2=Guangzhou") // 可选:为每个分片指定参数
                .build();
        JobTypeConfiguration typeConfig = new JobTypeConfiguration(jobClass);
        return LiteJobConfiguration.newBuilder(coreConfig, typeConfig)
                .overwrite(true) // 允许覆盖配置
                .build();
    }
}
// 简单任务实现
class MySimpleJob implements com.dangdang.ddframe.job.api.Job {
    @Override
    public void execute(ShardingContext shardingContext) {
        System.out.println("分片项: " + shardingContext.getShardingItem() + " 正在执行任务,当前时间: " + System.currentTimeMillis());
        // 你的业务逻辑
    }
}
// 数据处理流任务实现
class MyDataflowJob implements DataflowJob<String> {
    @Override
    public List<String> fetchData(ShardingContext shardingContext) {
        // 从数据源(如数据库)获取一批待处理数据
        System.out.println("分片项: " + shardingContext.getShardingItem() + " 正在获取数据...");
        // return fetchDataFromDB(shardingContext.getShardingItem());
        return null; // 示例中返回空
    }
    @Override
    public void processData(ShardingContext shardingContext, List<String> data) {
        // 处理获取到的数据
        System.out.println("分片项: " + shardingContext.getShardingItem() + " 正在处理数据: " + data);
        // processBatchData(data);
    }
}

总结与如何选择

特性 Spring @Scheduled Quartz Elastic-Job
易用性 ⭐⭐⭐⭐⭐ (非常简单) ⭐⭐⭐ (需要配置) ⭐⭐ (相对复杂)
功能丰富度 ⭐⭐ (基础) ⭐⭐⭐⭐⭐ (非常强大) ⭐⭐⭐⭐ (专注分布式)
持久化 ❌ (不支持) ✅ (支持) ✅ (支持)
集群 ❌ (不支持) ✅ (支持) ✅ (原生支持,强项)
动态管理 ❌ (不支持) ✅ (支持) ✅ (支持)
分片 ❌ (不支持) ❌ (不支持) ✅ (核心功能)
适用场景 单机应用,简单的定时任务 中大型应用,需要复杂调度和持久化的场景 分布式/微服务架构下的任务调度

选择建议:

  1. 如果你的应用是单体应用,定时任务需求简单(如:每天备份一次数据、每小时清理一次缓存),直接使用 Spring @Scheduled 即可,它足够简单且好用。

  2. 如果你的应用对定时任务有更高要求

    • 需要任务在服务器重启后依然存在(持久化)。
    • 需要动态地修改任务的执行时间或禁用/启用任务。
    • 需要构建集群环境,确保任务的高可用性。
    • 那么选择 Quartz 是一个稳妥且强大的方案。
  3. 如果你的应用是微服务架构

    • 你需要在多个服务实例上协同执行一个任务,但又不希望所有实例都执行一遍(避免重复)。
    • 你需要将一个大任务拆分成多个小任务,并行处理以提高效率(分片)。
    • 那么毫无疑问,Elastic-Job 是为你量身定做的分布式任务调度解决方案。
分享:
扫描分享到社交APP
上一篇
下一篇