Spring Boot实现定时任务案例

wen java案例 3

本文目录导读:

Spring Boot实现定时任务案例

  1. 项目依赖配置(pom.xml)
  2. 主启动类
  3. 方式一:使用@Scheduled注解(简单定时任务)
  4. 方式二:使用ScheduleConfig配置定时任务
  5. 方式三:使用Quartz框架(分布式定时任务)
  6. 动态定时任务管理
  7. 配置文件方式
  8. 使用配置文件属性的定时任务
  9. 线程池配置
  10. 测试类
  11. 使用说明
  12. 注意事项

我来为您提供一个完整的Spring Boot定时任务案例,包括多种实现方式。

项目依赖配置(pom.xml)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.14</version>
        <relativePath/>
    </parent>
    <groupId>com.example</groupId>
    <artifactId>scheduled-task-demo</artifactId>
    <version>1.0.0</version>
    <name>scheduled-task-demo</name>
    <description>Spring Boot定时任务示例</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!-- Spring Boot Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- Spring Boot Test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- Lombok 简化代码 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- 如果使用Quartz,需要添加以下依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-quartz</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

主启动类

package com.example.scheduled;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling  // 启用定时任务
public class ScheduledTaskApplication {
    public static void main(String[] args) {
        SpringApplication.run(ScheduledTaskApplication.class, args);
    }
}

方式一:使用@Scheduled注解(简单定时任务)

1 基础定时任务类

package com.example.scheduled.task;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
 * 基础定时任务示例
 */
@Slf4j
@Component
public class SimpleScheduledTasks {
    private static final DateTimeFormatter FORMATTER = 
        DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    /**
     * 固定速率执行(从任务开始时间计算)
     * 每5秒执行一次
     */
    @Scheduled(fixedRate = 5000)
    public void fixedRateTask() throws InterruptedException {
        log.info("固定速率任务开始, 时间: {}", LocalDateTime.now().format(FORMATTER));
        Thread.sleep(2000); // 模拟任务执行时间
        log.info("固定速率任务结束, 时间: {}", LocalDateTime.now().format(FORMATTER));
    }
    /**
     * 固定延迟执行(从任务结束时间计算)
     * 上一次任务执行完成后3秒再执行
     */
    @Scheduled(fixedDelay = 3000)
    public void fixedDelayTask() {
        log.info("固定延迟任务, 时间: {}", LocalDateTime.now().format(FORMATTER));
    }
    /**
     * 初始延迟 + 固定延迟
     * 启动5秒后执行,之后每次执行完毕后延迟2秒
     */
    @Scheduled(initialDelay = 5000, fixedDelay = 2000)
    public void initialDelayTask() {
        log.info("初始延迟任务, 时间: {}", LocalDateTime.now().format(FORMATTER));
    }
    /**
     * 使用cron表达式
     * 每秒执行一次
     */
    @Scheduled(cron = "0/1 * * * * ?")
    public void cronTask1() {
        log.info("每秒执行的任务, 时间: {}", LocalDateTime.now().format(FORMATTER));
    }
    /**
     * 使用cron表达式
     * 每天上午10点15分执行
     */
    @Scheduled(cron = "0 15 10 ? * *")
    public void cronTask2() {
        log.info("每天10:15执行的任务, 时间: {}", LocalDateTime.now().format(FORMATTER));
    }
    /**
     * 使用cron表达式
     * 每周一至周五的上午10:15执行
     */
    @Scheduled(cron = "0 15 10 ? * MON-FRI")
    public void cronTask3() {
        log.info("工作日10:15执行的任务, 时间: {}", LocalDateTime.now().format(FORMATTER));
    }
    /**
     * 使用cron表达式
     * 每月1号和15号的上午10:15执行
     */
    @Scheduled(cron = "0 15 10 1,15 * ?")
    public void cronTask4() {
        log.info("每月1号、15号10:15执行的任务, 时间: {}", LocalDateTime.now().format(FORMATTER));
    }
}

2 Cron表达式详解

package com.example.scheduled.config;
/**
 * Cron表达式说明
 * 
 * Cron表达式格式:秒 分钟 小时 日 月 星期 [年]
 * 
 * 字段说明:
 * 秒(0-59),分钟(0-59),小时(0-23),
 * 日(1-31),月(1-12或JAN-DEC),
 * 星期(1-7或SUN-SAT),年(可选)
 * 
 * 特殊字符:
 * * :任意值
 * ? :不指定值(用于日或星期)
 * - :区间
 * / :间隔
 * , :列表
 * L :
 * W :工作日
 * # :星期
 */
public class CronExpressionExample {
    // 常用示例
    /*
     * "0 0 12 * * ?"    每天中午12点触发
     * "0 15 10 ? * *"   每天上午10:15触发
     * "0 15 10 * * ?"   每天上午10:15触发
     * "0 15 10 * * ? *" 每天上午10:15触发
     * "0 15 10 * * ? 2019" 2019年的每天上午10:15触发
     * "0 * 14 * * ?"    每天下午2点到2:59期间的每1分钟触发
     * "0 0/5 14 * * ?"  每天下午2点到2:55期间的每5分钟触发
     * "0 0/5 14,18 * * ?" 每天下午2点到2:55和6点到6:55期间的每5分钟触发
     * "0 0-5 14 * * ?"  每天下午2点到2:05期间的每1分钟触发
     * "0 10,44 14 ? 3 WED" 每年三月的星期三的下午2:10和2:44触发
     * "0 15 10 ? * MON-FRI" 周一至周五的上午10:15触发
     * "0 15 10 15 * ?"  每月15日上午10:15触发
     * "0 15 10 L * ?"   每月最后一日的上午10:15触发
     * "0 15 10 ? * 6L"  每月的最后一个星期五上午10:15触发
     * "0 15 10 ? * 6#3" 每月的第三个星期五上午10:15触发
     */
}

方式二:使用ScheduleConfig配置定时任务

1 定时任务配置类

package com.example.scheduled.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.context.annotation.Bean;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.Executors;
/**
 * 定时任务配置类
 * 可以动态添加定时任务
 */
@Slf4j
@Configuration
public class ScheduleConfig implements SchedulingConfigurer {
    private static final DateTimeFormatter FORMATTER = 
        DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    /**
     * 配置线程池
     */
    @Bean
    public ThreadPoolTaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(10);  // 线程池大小
        scheduler.setThreadNamePrefix("scheduled-task-");  // 线程名称前缀
        scheduler.setAwaitTerminationSeconds(60);  // 优雅关闭等待时间
        scheduler.setWaitForTasksToCompleteOnShutdown(true);  // 关闭时等待任务完成
        return scheduler;
    }
    /**
     * 配置定时任务注册器
     */
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setTaskScheduler(taskScheduler());
        // 动态添加定时任务
        taskRegistrar.addFixedRateTask(() -> {
            log.info("动态固定速率任务, 时间: {}", LocalDateTime.now().format(FORMATTER));
        }, 10000, 5000);  // 初始延迟10秒,固定速率5秒
        taskRegistrar.addFixedDelayTask(() -> {
            log.info("动态固定延迟任务, 时间: {}", LocalDateTime.now().format(FORMATTER));
        }, 10000, 3000);  // 初始延迟10秒,固定延迟3秒
        taskRegistrar.addCronTask(() -> {
            log.info("动态Cron任务, 时间: {}", LocalDateTime.now().format(FORMATTER));
        }, "0/30 * * * * ?");  // 每30秒执行
    }
}

方式三:使用Quartz框架(分布式定时任务)

1 Quartz任务类

package com.example.scheduled.quartz;
import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
 * Quartz任务示例
 */
@Slf4j
public class QuartzTask implements Job {
    private static final DateTimeFormatter FORMATTER = 
        DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        log.info("Quartz任务执行, 时间: {}", LocalDateTime.now().format(FORMATTER));
        // 获取JobDataMap中的参数
        String param = context.getJobDetail().getJobDataMap().getString("param");
        log.info("任务参数: {}", param);
        // 执行具体的业务逻辑
        try {
            // 模拟业务处理
            Thread.sleep(1000);
            log.info("Quartz任务执行完成");
        } catch (InterruptedException e) {
            log.error("任务执行失败", e);
            Thread.currentThread().interrupt();
        }
    }
}

2 Quartz配置类

package com.example.scheduled.config;
import com.example.scheduled.quartz.QuartzTask;
import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * Quartz配置类
 */
@Configuration
public class QuartzConfig {
    /**
     * 创建JobDetail
     */
    @Bean
    public JobDetail quartzJobDetail() {
        return JobBuilder.newJob(QuartzTask.class)
                .withIdentity("quartzTask")  // Job名称
                .usingJobData("param", "test-param")  // 传递参数
                .storeDurably()  // 即使没有Trigger关联也保留
                .build();
    }
    /**
     * 创建Trigger
     */
    @Bean
    public Trigger quartzTrigger() {
        // 每10秒执行一次
        CronScheduleBuilder cronSchedule = 
            CronScheduleBuilder.cronSchedule("0/10 * * * * ?");
        return TriggerBuilder.newTrigger()
                .forJob(quartzJobDetail())
                .withIdentity("quartzTrigger")  // Trigger名称
                .withSchedule(cronSchedule)
                .build();
    }
}

3 使用Scheduler动态调度

package com.example.scheduled.service;
import com.example.scheduled.quartz.QuartzTask;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
/**
 * 动态调度服务
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class QuartzSchedulerService {
    private final Scheduler scheduler;
    /**
     * 启动时初始化
     */
    @PostConstruct
    public void init() {
        try {
            scheduler.start();
        } catch (SchedulerException e) {
            log.error("Scheduler启动失败", e);
        }
    }
    /**
     * 添加定时任务
     */
    public void addJob(String jobName, String cronExpression) throws SchedulerException {
        JobDetail jobDetail = JobBuilder.newJob(QuartzTask.class)
                .withIdentity(jobName)
                .usingJobData("param", jobName)
                .build();
        CronTrigger trigger = TriggerBuilder.newTrigger()
                .withIdentity(jobName + "Trigger")
                .withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
                .build();
        scheduler.scheduleJob(jobDetail, trigger);
        log.info("添加定时任务: {}, cron: {}", jobName, cronExpression);
    }
    /**
     * 暂停定时任务
     */
    public void pauseJob(String jobName) throws SchedulerException {
        scheduler.pauseJob(JobKey.jobKey(jobName));
        log.info("暂停定时任务: {}", jobName);
    }
    /**
     * 恢复定时任务
     */
    public void resumeJob(String jobName) throws SchedulerException {
        scheduler.resumeJob(JobKey.jobKey(jobName));
        log.info("恢复定时任务: {}", jobName);
    }
    /**
     * 删除定时任务
     */
    public void deleteJob(String jobName) throws SchedulerException {
        scheduler.deleteJob(JobKey.jobKey(jobName));
        log.info("删除定时任务: {}", jobName);
    }
}

动态定时任务管理

package com.example.scheduled.controller;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
/**
 * 动态定时任务管理控制器
 */
@Slf4j
@RestController
@RequestMapping("/api/task")
@RequiredArgsConstructor
public class DynamicTaskController {
    private final TaskScheduler taskScheduler;
    // 存储动态任务
    private final Map<String, ScheduledFuture<?>> taskMap = new ConcurrentHashMap<>();
    /**
     * 创建任务调度器
     */
    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(10);
        scheduler.setThreadNamePrefix("dynamic-task-");
        return scheduler;
    }
    /**
     * 添加动态任务
     */
    @PostMapping("/add")
    public String addTask(@RequestParam String taskName, 
                          @RequestParam String cron) {
        // 检查任务是否已存在
        if (taskMap.containsKey(taskName)) {
            return "任务已存在: " + taskName;
        }
        // 创建定时任务
        ScheduledFuture<?> future = taskScheduler.schedule(() -> {
            log.info("动态任务[{}]执行, 时间: {}", taskName, 
                java.time.LocalDateTime.now());
        }, new CronTrigger(cron));
        taskMap.put(taskName, future);
        log.info("添加动态任务: {}, cron: {}", taskName, cron);
        return "任务添加成功: " + taskName;
    }
    /**
     * 删除动态任务
     */
    @DeleteMapping("/cancel")
    public String cancelTask(@RequestParam String taskName) {
        ScheduledFuture<?> future = taskMap.get(taskName);
        if (future != null) {
            future.cancel(true);
            taskMap.remove(taskName);
            log.info("取消动态任务: {}", taskName);
            return "任务取消成功: " + taskName;
        }
        return "任务不存在: " + taskName;
    }
    /**
     * 获取所有任务
     */
    @GetMapping("/list")
    public Map<String, ScheduledFuture<?>> getTaskList() {
        return taskMap;
    }
}

配置文件方式

1 application.yml配置

spring:
  application:
    name: scheduled-task-demo
  # Quartz配置
  quartz:
    job-store-type: memory  # 使用内存存储任务
    wait-for-jobs-to-complete-on-shutdown: true  # 关闭时等待任务完成
    overwrite-existing-jobs: false  # 不覆盖已存在的任务
    properties:
      org:
        quartz:
          scheduler:
            instanceName: MyScheduler
            instanceId: AUTO
          threadPool:
            maxConcurrency: 10
            threadCount: 10
            threadPriority: 5
            threadsInheritContextClassLoaderOfInitializingThread: true
          jobStore:
            class: org.quartz.simpl.RAMJobStore  # 内存存储
# 自定义配置
scheduled:
  # 定时任务开关
  enabled: true
  # 任务线程池大小
  pool-size: 10
  # 固定速率任务间隔(毫秒)
  fixed-rate-interval: 5000
  # 固定延迟任务间隔(毫秒)
  fixed-delay-interval: 3000
  # 初始延迟(毫秒)
  initial-delay: 1000

使用配置文件属性的定时任务

package com.example.scheduled.task;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
/**
 * 配置文件属性定时任务
 */
@Slf4j
@Component
public class ConfigScheduledTasks {
    @Value("${scheduled.fixed-rate-interval}")
    private long fixedRateInterval;
    @Value("${scheduled.fixed-delay-interval}")
    private long fixedDelayInterval;
    @Value("${scheduled.initial-delay}")
    private long initialDelay;
    /**
     * 动态读取配置的固定速率
     */
    @Scheduled(fixedRateString = "${scheduled.fixed-rate-interval}")
    public void fixedRateWithConfig() {
        log.info("配置固定速率任务, 间隔: {}ms", fixedRateInterval);
    }
    /**
     * 动态读取配置的固定延迟
     */
    @Scheduled(fixedDelayString = "${scheduled.fixed-delay-interval}")
    public void fixedDelayWithConfig() {
        log.info("配置固定延迟任务, 延迟: {}ms", fixedDelayInterval);
    }
    /**
     * 动态读取配置的初始延迟
     */
    @Scheduled(initialDelayString = "${scheduled.initial-delay}", 
               fixedDelayString = "${scheduled.fixed-delay-interval}")
    public void initialDelayWithConfig() {
        log.info("配置初始延迟任务, 初始延迟: {}ms", initialDelay);
    }
}

线程池配置

package com.example.scheduled.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import java.util.concurrent.ThreadPoolExecutor;
/**
 * 线程池配置
 */
@Configuration
public class ThreadPoolConfig {
    /**
     * 任务执行线程池
     */
    @Bean(name = "scheduledTaskExecutor")
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);      // 核心线程数
        executor.setMaxPoolSize(20);       // 最大线程数
        executor.setQueueCapacity(100);    // 队列容量
        executor.setKeepAliveSeconds(60);  // 线程空闲时间
        executor.setThreadNamePrefix("task-executor-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setAwaitTerminationSeconds(60);
        executor.initialize();
        return executor;
    }
    /**
     * 定时任务调度器
     */
    @Bean(name = "scheduledTaskScheduler")
    public ThreadPoolTaskScheduler scheduledTaskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(10);
        scheduler.setThreadNamePrefix("scheduled-scheduler-");
        scheduler.setWaitForTasksToCompleteOnShutdown(true);
        scheduler.setAwaitTerminationSeconds(60);
        scheduler.setRemoveOnCancelPolicy(true);
        scheduler.initialize();
        return scheduler;
    }
}

测试类

package com.example.scheduled;
import com.example.scheduled.service.QuartzSchedulerService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class ScheduledTaskApplicationTests {
    @Autowired
    private QuartzSchedulerService quartzSchedulerService;
    @Test
    void contextLoads() {
    }
    @Test
    void testQuartz() throws Exception {
        // 添加一个定时任务
        quartzSchedulerService.addJob("testJob", "0/5 * * * * ?");
        Thread.sleep(10000); // 等待10秒
        // 暂停任务
        quartzSchedulerService.pauseJob("testJob");
        Thread.sleep(5000);
        // 恢复任务
        quartzSchedulerService.resumeJob("testJob");
        Thread.sleep(5000);
        // 删除任务
        quartzSchedulerService.deleteJob("testJob");
    }
}

使用说明

  1. @Scheduled注解方式:适合简单的定时任务,使用方便,直接注解即可
  2. SchedulingConfigurer方式:适合需要动态添加任务的场景
  3. Quartz方式:适合复杂的定时任务场景,支持分布式、持久化等
  4. 动态任务管理:适合需要运行时动态增删任务的场景

注意事项

  1. 定时任务默认是单线程执行的,需要配置线程池
  2. @ScheduledfixedRate从任务开始时间计算,fixedDelay从任务结束时间计算
  3. Cron表达式要确保正确,否则可能导致任务不执行
  4. 在分布式环境下,需要考虑任务的幂等性和分布式锁的问题

这个案例提供了多种定时任务的实现方式,您可以根据实际需求选择合适的方式。

抱歉,评论功能暂时关闭!