Spring Task定时任务的完整使用教学

 更新时间:2026年06月02日 09:33:21   作者:河阿里  
Spring Task是Spring框架提供的轻量级定时任务工具,它无需依赖额外的第三方库,直接集成在Spring核心包中,在SpringBoot中,Spring Task提供了自动配置支持,只需少量注解即可快速实现定时任务功能,本文给大家介绍了Spring Task定时任务的完整使用教学

一、Spring Task 概述

1.1 核心优势

  • 轻量级:无需额外依赖,Spring 核心包自带
  • 简单易用:基于注解配置,上手快
  • 集成度高:与 Spring 生态无缝集成
  • 功能完善:支持多种触发方式、异步执行、异常处理等

1.2 适用场景

  • 数据定时备份与清理
  • 定时发送邮件/短信通知
  • 定时统计报表生成
  • 系统状态定时监控
  • 定时同步第三方数据

二、案例:5分钟实现第一个定时任务

2.1 环境准备

创建一个标准的 SpringBoot 项目,无需添加额外依赖(SpringBoot 2.x 及以上版本已默认包含 spring-context 依赖)。

2.2 开启定时任务支持

在 SpringBoot 启动类上添加 @EnableScheduling 注解,开启定时任务自动配置:

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

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

2.3 创建定时任务类

创建一个普通的 Spring Bean,在需要定时执行的方法上添加 @Scheduled 注解:

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

@Component
public class SimpleTask {
    private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    // 每隔5秒执行一次
    @Scheduled(fixedRate = 5000)
    public void printCurrentTime() {
        System.out.println("当前时间:" + LocalDateTime.now().format(formatter));
    }
}

2.4 运行测试

启动 SpringBoot 应用,控制台会每隔5秒输出一次当前时间,说明定时任务已成功运行。

三、核心注解详解

3.1 @EnableScheduling

  • 作用:开启 Spring 定时任务的自动配置
  • 位置:通常添加在启动类或配置类上
  • 原理:该注解会导入 SchedulingConfiguration 配置类,自动注册 ScheduledAnnotationBeanPostProcessor,用于扫描带有 @Scheduled 注解的方法并创建定时任务

3.2 @Scheduled

@Scheduled 是最核心的注解,用于标记一个方法为定时任务方法。它提供了多种属性来配置任务的执行规则:

属性类型说明
cron(重点)String使用 cron 表达式定义执行规则
fixedRatelong从上一次执行开始时间算起,间隔指定毫秒数执行
fixedDelaylong从上一次执行结束时间算起,间隔指定毫秒数执行
initialDelaylong首次执行延迟的毫秒数
zoneStringcron 表达式使用的时区,默认使用服务器本地时区

四、定时任务触发方式详解

4.1 fixedRate 固定频率执行

特点:从上一次任务开始执行的时间点开始计算,间隔指定时间后执行下一次任务。

// 每隔5秒执行一次,无论上一次任务执行了多久
@Scheduled(fixedRate = 5000)
public void fixedRateTask() {
    System.out.println("fixedRate任务执行:" + LocalDateTime.now());
}

注意:如果任务执行时间超过了 fixedRate 设置的间隔时间,下一次任务会立即执行,不会等待。例如:任务执行需要8秒,fixedRate=5秒,那么任务会连续执行,没有间隔。

4.2 fixedDelay 固定延迟执行

特点:从上一次任务执行结束的时间点开始计算,间隔指定时间后执行下一次任务。

// 上一次任务执行结束后,延迟3秒执行下一次
@Scheduled(fixedDelay = 3000)
public void fixedDelayTask() {
    System.out.println("fixedDelay任务执行:" + LocalDateTime.now());
}

适用场景:任务执行时间不固定,需要确保两次任务之间有固定的间隔时间。

4.3 initialDelay 首次执行延迟

initialDelay 可以与 fixedRatefixedDelay 配合使用,指定应用启动后,首次执行任务的延迟时间。

// 应用启动后延迟10秒,然后每隔5秒执行一次
@Scheduled(initialDelay = 10000, fixedRate = 5000)
public void initialDelayTask() {
    System.out.println("initialDelay任务执行:" + LocalDateTime.now());
}

4.4 cron 表达式(最灵活)

cron 表达式是一种强大的时间表达式,可以精确到秒,定义复杂的执行规则。

cron 表达式结构

cron 表达式由6或7个字段组成,空格分隔:

秒 分 时 日 月 周 [年]
  • 年字段是可选的,通常省略
  • 每个字段可以使用特殊字符表示不同的含义

常用特殊字符

  • *:表示该字段的所有可能值
  • ?:表示不指定值,用于日和周字段互斥
  • -:表示范围
  • ,:表示枚举多个值
  • /:表示步长

常用 cron 表达式示例

// 每天凌晨0点执行
@Scheduled(cron = "0 0 0 * * ?")
public void dailyTask() {}

// 每天上午9点到下午6点,每隔半小时执行一次
@Scheduled(cron = "0 0/30 9-18 * * ?")
public void workHourTask() {}

// 每周一至周五的上午10点15分执行
@Scheduled(cron = "0 15 10 ? * MON-FRI")
public void weekdayTask() {}

// 每月1号凌晨1点执行
@Scheduled(cron = "0 0 1 1 * ?")
public void monthlyTask() {}

// 每10秒执行一次
@Scheduled(cron = "*/10 * * * * ?")
public void every10SecondsTask() {}

在线 cron 表达式生成工具

五、任务执行器(TaskExecutor)配置

5.1 默认执行器的问题

Spring Task 默认使用单线程的任务执行器。如果有多个定时任务,它们会排队执行,一个任务阻塞会导致其他任务延迟执行。

5.2 自定义线程池

创建一个配置类,实现 SchedulingConfigurer 接口,自定义任务执行器:

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

@Configuration
public class SchedulerConfig implements SchedulingConfigurer {

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        // 核心线程数
        scheduler.setPoolSize(10);
        // 线程名前缀
        scheduler.setThreadNamePrefix("task-scheduler-");
        // 等待任务完成后关闭
        scheduler.setWaitForTasksToCompleteOnShutdown(true);
        // 等待时间
        scheduler.setAwaitTerminationSeconds(60);
        // 初始化
        scheduler.initialize();
        
        taskRegistrar.setTaskScheduler(scheduler);
    }
}

5.3 配置参数说明

  • setPoolSize(int poolSize):设置线程池核心线程数,建议根据任务数量和执行时间调整
  • setThreadNamePrefix(String prefix):设置线程名前缀,方便日志排查
  • setWaitForTasksToCompleteOnShutdown(boolean):设置是否等待任务完成后关闭线程池
  • setAwaitTerminationSeconds(int seconds):设置等待任务完成的最长时间

六、异步定时任务

6.1 需求情况

即使配置了多线程的任务执行器,同一个任务的多次执行仍然是串行的。如果任务执行时间较长,会导致任务堆积。

使用异步定时任务可以让同一个任务的多次执行并行处理。

6.2 开启异步支持

在启动类或配置类上添加 @EnableAsync 注解:

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

@SpringBootApplication
@EnableScheduling
@EnableAsync // 开启异步支持
public class TaskApplication {
    public static void main(String[] args) {
        SpringApplication.run(TaskApplication.class, args);
    }
}

6.3 创建异步定时任务

在定时任务方法上添加 @Async 注解:

import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class AsyncTask {

    @Async
    @Scheduled(fixedRate = 1000)
    public void asyncTask() throws InterruptedException {
        System.out.println("异步任务开始执行:" + Thread.currentThread().getName());
        // 模拟任务执行3秒
        Thread.sleep(3000);
        System.out.println("异步任务执行结束:" + Thread.currentThread().getName());
    }
}

运行后会发现,即使任务执行需要3秒,仍然会每隔1秒启动一个新的任务,每个任务在不同的线程中执行。

6.4 自定义异步线程池

默认的异步线程池可能无法满足生产环境需求,建议自定义异步线程池:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

@Configuration
public class AsyncConfig {

    @Bean("taskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心线程数
        executor.setCorePoolSize(10);
        // 最大线程数
        executor.setMaxPoolSize(20);
        // 队列容量
        executor.setQueueCapacity(200);
        // 线程存活时间
        executor.setKeepAliveSeconds(60);
        // 线程名前缀
        executor.setThreadNamePrefix("async-task-");
        // 拒绝策略:由调用线程执行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 初始化
        executor.initialize();
        return executor;
    }
}

使用时指定线程池名称:

@Async("taskExecutor")
@Scheduled(fixedRate = 1000)
public void asyncTask() {}

七、动态定时任务

7.1 为什么需要动态定时任务

使用 @Scheduled 注解配置的定时任务是静态的,一旦应用启动,执行规则就无法修改。

动态定时任务允许在应用运行时修改任务的执行时间、暂停、恢复和删除任务。

7.2 实现方式

通过 ScheduledTaskRegistrar 注册动态任务:

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;

import java.util.concurrent.ScheduledFuture;

@Configuration
public class DynamicTaskConfig implements SchedulingConfigurer {

    private ScheduledTaskRegistrar taskRegistrar;
    private ScheduledFuture<?> future;

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        this.taskRegistrar = taskRegistrar;
    }

    // 添加任务
    public void addTask(Runnable task, String cron) {
        if (future != null) {
            future.cancel(true);
        }
        future = taskRegistrar.getScheduler().schedule(task, new CronTrigger(cron));
    }

    // 暂停任务
    public void stopTask() {
        if (future != null) {
            future.cancel(true);
        }
    }
}

7.3 动态修改任务执行时间

创建一个控制器,提供接口来动态修改任务执行时间:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;

@RestController
public class DynamicTaskController {

    @Autowired
    private DynamicTaskConfig dynamicTaskConfig;

    @GetMapping("/updateTask")
    public String updateTask(@RequestParam String cron) {
        Runnable task = () -> System.out.println("动态任务执行:" + LocalDateTime.now());
        dynamicTaskConfig.addTask(task, cron);
        return "任务已更新,新的cron表达式:" + cron;
    }

    @GetMapping("/stopTask")
    public String stopTask() {
        dynamicTaskConfig.stopTask();
        return "任务已暂停";
    }
}

八、任务异常处理

8.1 默认异常处理

默认情况下,如果定时任务方法抛出异常,该任务会终止执行,不会影响其他任务。

8.2 全局异常处理

实现 ErrorHandler 接口,创建全局异常处理器:

import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.util.ErrorHandler;

import java.lang.reflect.Method;

@Configuration
public class TaskExceptionConfig implements SchedulingConfigurer, AsyncConfigurer {

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(10);
        scheduler.setThreadNamePrefix("task-scheduler-");
        // 设置异常处理器
        scheduler.setErrorHandler(new TaskErrorHandler());
        scheduler.initialize();
        taskRegistrar.setTaskScheduler(scheduler);
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new AsyncTaskErrorHandler();
    }

    // 同步任务异常处理器
    public static class TaskErrorHandler implements ErrorHandler {
        @Override
        public void handleError(Throwable t) {
            System.err.println("同步定时任务执行异常:" + t.getMessage());
            t.printStackTrace();
        }
    }

    // 异步任务异常处理器
    public static class AsyncTaskErrorHandler implements AsyncUncaughtExceptionHandler {
        @Override
        public void handleUncaughtException(Throwable ex, Method method, Object... params) {
            System.err.println("异步定时任务执行异常:" + method.getName());
            ex.printStackTrace();
        }
    }
}

8.3 方法级异常处理

在定时任务方法内部使用 try-catch 块处理异常:

@Scheduled(fixedRate = 5000)
public void taskWithExceptionHandling() {
    try {
        // 业务逻辑
        int result = 1 / 0;
    } catch (Exception e) {
        System.err.println("任务执行异常:" + e.getMessage());
        // 记录日志、发送告警等
    }
}

九、任务并发与幂等性

9.1 并发问题

在以下场景中,定时任务可能会出现并发执行的问题:

  1. 使用异步定时任务
  2. 应用集群部署
  3. 任务执行时间超过执行间隔

9.2 幂等性设计

为了避免并发执行导致的数据问题,定时任务必须保证幂等性。常用的实现方式:

  1. 数据库唯一约束:在关键表上添加唯一索引
  2. 分布式锁:使用 Redis、Zookeeper 等实现分布式锁
  3. 状态机:记录任务执行状态,避免重复执行

9.3 Redis 分布式锁实现

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

@Component
public class IdempotentTask {

    @Autowired
    private StringRedisTemplate redisTemplate;

    private static final String LOCK_KEY = "task:lock:idempotent";
    private static final long LOCK_EXPIRE = 30; // 锁过期时间,秒

    @Scheduled(fixedRate = 5000)
    public void idempotentTask() {
        // 尝试获取锁
        Boolean locked = redisTemplate.opsForValue()
                .setIfAbsent(LOCK_KEY, "locked", LOCK_EXPIRE, TimeUnit.SECONDS);
        
        if (Boolean.TRUE.equals(locked)) {
            try {
                // 执行业务逻辑
                System.out.println("获取到锁,执行任务");
            } finally {
                // 释放锁
                redisTemplate.delete(LOCK_KEY);
            }
        } else {
            System.out.println("未获取到锁,跳过本次执行");
        }
    }
}

十、集群环境下的定时任务

10.1 集群环境的问题

在集群环境下,每个应用实例都会执行定时任务,导致任务重复执行。

10.2 解决方案

  1. 分布式锁:如上面的 Redis 分布式锁实现
  2. 任务调度中心:使用 XXL-Job、Elastic-Job 等专业的分布式任务调度框架
  3. 单独部署:将定时任务单独部署在一个实例上

10.3 推荐方案

对于简单的定时任务,使用 Redis 分布式锁即可满足需求。对于复杂的任务调度需求,建议使用专业的分布式任务调度框架,如 XXL-Job。

十一、常见问题

11.1 定时任务不执行

可能原因

  1. 忘记添加 @EnableScheduling 注解
  2. 定时任务类没有被 Spring 扫描到(没有添加 @Component 等注解)
  3. 方法不是 public 的
  4. 方法有参数
  5. 方法返回值不是 void

解决方案:检查以上几点,确保配置正确。

11.2 任务执行时间不准确

可能原因

  1. 使用了默认的单线程执行器,任务被阻塞
  2. 服务器时间不准确
  3. cron 表达式时区设置不正确

解决方案

  1. 配置多线程任务执行器
  2. 校准服务器时间
  3. @Scheduled 注解中指定时区:@Scheduled(cron = "0 0 0 * * ?", zone = "Asia/Shanghai")

11.3 任务重复执行

可能原因

  1. 应用集群部署
  2. 任务执行时间超过执行间隔
  3. 异步任务没有控制并发

解决方案

  1. 使用分布式锁
  2. 调整任务执行间隔
  3. 控制异步任务的并发数

十二、总结

  1. 合理配置线程池:根据任务数量和执行时间调整线程池大小,避免任务阻塞
  2. 使用异步任务:对于执行时间较长的任务,使用异步执行
  3. 保证幂等性:所有定时任务都应该设计为幂等的
  4. 异常处理:添加全局异常处理和方法级异常处理,避免任务终止
  5. 日志记录:在任务执行前后记录详细日志,方便问题排查
  6. 监控告警:对任务执行情况进行监控,异常时及时告警
  7. 避免长任务:尽量将长任务拆分为多个短任务
  8. 集群环境使用分布式锁:避免任务重复执行

以上就是Spring Task定时任务的完整使用教学的详细内容,更多关于Spring Task定时任务使用的资料请关注脚本之家其它相关文章!

相关文章

  • Mybatis设置sql打印日志的多种方法

    Mybatis设置sql打印日志的多种方法

    这篇文章主要介绍了Mybatis设置sql打印日志,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-08-08
  • 浅谈标签和JLabel类构造方法

    浅谈标签和JLabel类构造方法

    这篇文章主要介绍了标签和JLabel类构造方法,具有一定参考价值,需要的朋友可以参考下。
    2017-09-09
  • Java 常见异常(Runtime Exception )详细介绍并总结

    Java 常见异常(Runtime Exception )详细介绍并总结

    这篇文章主要介绍了Java 常见异常(Runtime Exception )详细介绍并相关资料,大家在开发Java 应用软件的时候经常会遇到各种异常这里帮大家整理了一部分,并解释如何解决,需要的朋友可以参考下
    2016-10-10
  • eclipse构建和发布maven项目的教程

    eclipse构建和发布maven项目的教程

    这篇文章主要为大家详细介绍了eclipse构建和发布maven项目的教程,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-03-03
  • SpringBoot整合screw实现数据库文档自动生成的示例代码

    SpringBoot整合screw实现数据库文档自动生成的示例代码

    这篇文章主要介绍了SpringBoot整合screw实现数据库文档自动生成的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-09-09
  • 详解Java中多线程异常捕获Runnable的实现

    详解Java中多线程异常捕获Runnable的实现

    这篇文章主要介绍了详解Java中多线程异常捕获Runnable的实现的相关资料,希望通过本文能帮助到大家,让大家理解掌握这样的知识,需要的朋友可以参考下
    2017-10-10
  • 通过Spring层面进行事务回滚的实现

    通过Spring层面进行事务回滚的实现

    本文主要介绍了通过Spring层面进行事务回滚的实现,包括声明式事务和编程式事务,具有一定的参考价值,感兴趣的可以了解一下
    2025-04-04
  • SpringBoot注册Servlet的三种方法详解

    SpringBoot注册Servlet的三种方法详解

    这篇文章主要介绍了SpringBoot注册Servlet的三种方法详解,教你如何Spring Boot 注册 Servlet、Filter、Listener,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2019-05-05
  • Java方法重载和重写原理区别解析

    Java方法重载和重写原理区别解析

    这篇文章主要介绍了Java方法重载和重写原理区别解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-03-03
  • 浅谈Spring refresh的工作流程

    浅谈Spring refresh的工作流程

    这篇文章主要介绍了浅谈Spring refresh的工作流程,refresh 是 AbstractApplicationContext 中的一个方法,负责初始化 ApplicationContext容器,让我们一起来学习一下吧
    2023-04-04

最新评论