杰瑞科技汇

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

Spring 框架提供了非常灵活的定时任务支持,主要可以分为以下三类:

Spring Java定时任务如何配置与执行?-图1
(图片来源网络,侵删)
  1. 注解驱动(最常用、最简单):使用 @Scheduled 注解,配置简单,适合大多数场景。
  2. 接口驱动(更灵活):实现 SchedulingConfigurer 接口,可以更灵活地配置线程池。
  3. 外部化配置(最强大):集成 QuartzElastic-Job 等专业调度框架,适合分布式、高可用的复杂场景。

下面我们逐一详细介绍。


@Scheduled 注解驱动(推荐)

这是 Spring Boot 中最流行、最简单的方式,你只需要在配置类上开启定时任务,然后在方法上使用 @Scheduled 注解即可。

添加依赖

pom.xml 中,确保引入 spring-boot-starter,它已经包含了定时任务的核心依赖 spring-context

<!-- 对于 Spring Boot 项目,通常不需要手动添加,spring-boot-starter 已包含 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>

开启定时任务

在你的 Spring Boot 启动类或者一个专门的配置类上,添加 @EnableScheduling 注解来启用对 @Scheduled 的支持。

Spring Java定时任务如何配置与执行?-图2
(图片来源网络,侵删)
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);
    }
}

编写定时任务方法

创建一个 Service 或 Component,然后在需要定时执行的方法上添加 @Scheduled 注解。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@Component
public class MyScheduledTasks {
    private static final Logger log = LoggerFactory.getLogger(MyScheduledTasks.class);
    private static final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    /**
     * fixedRate: 固定速率执行,意思是两次任务的开始时间间隔固定,不受任务执行时间影响。
     * fixedRate = 5000,表示每5秒执行一次,如果上次任务执行了2秒,那么它会在3秒后立即开始下一次。
     */
    @Scheduled(fixedRate = 5000)
    public void taskWithFixedRate() {
        log.info("=== Fixed Rate Task: 当前时间 {} ===", dtf.format(LocalDateTime.now()));
        // 模拟一个耗时3秒的任务
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    /**
     * fixedDelay: 固定延迟执行,意思是上次任务**结束**后,等待固定时间再开始下一次。
     * fixedDelay = 5000,如果上次任务执行了2秒,那么它会在任务结束后再等5秒,总共7秒后开始下一次。
     */
    @Scheduled(fixedDelay = 5000)
    public void taskWithFixedDelay() {
        log.info("=== Fixed Delay Task: 当前时间 {} ===", dtf.format(LocalDateTime.now()));
    }
    /**
     * initialDelay: 首次延迟执行,可以与 fixedRate 或 fixedDelay 组合使用,表示在应用启动后等待多久再开始第一次执行。
     */
    @Scheduled(initialDelay = 10000, fixedRate = 5000)
    public void taskWithInitialDelay() {
        log.info("=== Initial Delay & Fixed Rate Task: 当前时间 {} ===", dtf.format(LocalDateTime.now()));
    }
    /**
     * cron: 功能最强大的表达式,可以设置更复杂的执行时间规则。
     * cron = "0/10 * * * * ?" 表示从0秒开始,每10秒执行一次。
     * 格式:[秒] [分] [时] [日] [月] [周几] [年(可选)]
     * 通配符:
     *   * : 任意值
     *   ? : 不指定值(用于日和周几冲突时)
     *   - : 范围,如 1-5
     *   , : 列表,如 1,3,5
     *   / : 步长,如 0/10 表示从0开始,每10秒
     */
    @Scheduled(cron = "0/15 * * * * ?")
    public void taskWithCron() {
        log.info("=== Cron Task: 当前时间 {} ===", dtf.format(LocalDateTime.now()));
    }
}

配置线程池(可选)

默认情况下,Spring Boot 使用一个单线程的 ThreadPoolTaskScheduler 来执行所有定时任务,如果你的任务很多或某个任务耗时很长,可能会导致任务阻塞或延迟。

你可以通过在 application.propertiesapplication.yml 中配置来优化:

application.properties

Spring Java定时任务如何配置与执行?-图3
(图片来源网络,侵删)
# 核心线程池大小
spring.task.scheduling.pool.size=5
# 线程名前缀
spring.task.scheduling.thread-name-prefix=scheduling-
# 等待任务在关闭时完成的最大秒数
spring.task.scheduling.shutdown.await-termination=10

application.yml

spring:
  task:
    scheduling:
      pool:
        size: 5
      thread-name-prefix: scheduling-
      shutdown:
        await-termination: 10

SchedulingConfigurer 接口驱动

当你需要更灵活地配置线程池(动态设置线程池大小)时,可以实现 SchedulingConfigurer 接口。

创建配置类

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 SchedulingConfig implements SchedulingConfigurer {
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        // 使用一个固定大小的线程池
        // 这里创建一个大小为10的线程池
        taskRegistrar.setScheduler(Executors.newScheduledThreadPool(10));
    }
}

这种方式让你可以完全控制任务调度器的创建,但通常 application.properties 的配置已经足够,所以此方法用得相对较少。


集成专业调度框架(Quartz)

当你的应用是分布式部署,或者对任务的高可用性、持久化、集群管理有很高要求时,@Scheduled 就显得力不从心了,这时,集成 Quartz 是最佳选择。

Quartz 是一个功能强大、开源的作业调度库。

添加依赖

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

创建 Job(任务)

Quartz 的任务需要继承 QuartzJobBean

import org.quartz.*;
import org.springframework.stereotype.Component;
@Component
public class MyQuartzJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        // 获取 JobDetail 中的数据
        JobDataMap dataMap = context.getJobDetail().getJobDataMap();
        String taskName = dataMap.getString("taskName");
        System.out.println("Quartz Task is running... Task Name: " + taskName);
        // 这里编写你的业务逻辑
    }
}

配置和触发 Job

你可以在配置类中定义 Job 和 Trigger(触发器),并启动调度。

import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class QuartzConfig {
    // 1. 定义 Job
    @Bean
    public JobDetail myQuartzJobDetail() {
        // 关联我们自己的 Job 类
        // 使用持久化,这样即使应用重启,任务信息也不会丢失
        return JobBuilder.newJob(MyQuartzJob.class)
                .withIdentity("myQuartzJob") // 任务名称
                .usingJobData("taskName", "这是一个Quartz任务") // 传递给Job的数据
                .storeDurably() // 即使没有关联Trigger也进行保存
                .build();
    }
    // 2. 定义 Trigger
    @Bean
    public Trigger myQuartzTrigger() {
        // 简单的触发器,每10秒执行一次
        SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
                .withIntervalInSeconds(10)
                .repeatForever();
        return TriggerBuilder.newTrigger()
                .forJob(myQuartzJobDetail()) // 关联Job
                .withIdentity("myQuartzTrigger") // 触发器名称
                .withSchedule(scheduleBuilder)
                .build();
    }
}

Quartz 的优势

  • 持久化:可以将任务信息保存到数据库中,应用重启后任务不会丢失。
  • 集群:支持多节点部署,自动实现任务的负载均衡和故障转移。
  • 丰富的 API:提供了更复杂的调度功能,如日历、任务依赖等。
  • 管理界面:可以通过管理界面查看、暂停、恢复、删除任务。

总结与如何选择

特性 @Scheduled 注解 SchedulingConfigurer 接口 Quartz 框架
使用复杂度 非常简单 简单 较复杂
适用场景 单机应用,简单、固定的定时任务 需要自定义线程池配置 分布式、高可用、复杂调度
持久化 不支持 (应用重启任务丢失) 不支持 支持 (数据库存储)
集群支持 不支持 不支持 支持
灵活性 一般(依赖cron表达式) 较高(线程池控制) 非常高 (API丰富)
依赖 Spring 内置 Spring 内置 需要额外引入 Quartz

选择建议:

  1. 如果你只是想在 Spring Boot 应用里加几个简单的定时任务,比如每天凌晨清理数据、每小时同步一次信息,直接使用 @Scheduled,这是 90% 的场景下的最佳选择。
  2. 如果你的定时任务很多,或者某个任务可能耗时较长,导致其他任务被延迟,可以通过配置 application.properties 中的线程池来解决,如果还不够灵活,再考虑 SchedulingConfigurer
  3. 如果你的应用是微服务架构,部署在多台机器上,需要确保定时任务在集群中只执行一次,或者要求任务在应用崩溃重启后能自动恢复,那么必须使用 Quartz 或其他分布式调度框架(如 Elastic-Job)。
分享:
扫描分享到社交APP
上一篇
下一篇