Bootstrap

Springboot自带注解@Scheduled实现定时任务

基于@Scheduled注解实现简单定时任务

原理

Spring Boot 提供了@Scheduled注解,通过在方法上添加此注解,可以方便地将方法配置为定时任务。在应用启动时,Spring 会自动扫描带有@Scheduled注解的方法,并根据注解中的参数来确定任务的执行时间。这些参数可以使用cron表达式、固定延迟(fixedDelay)或者固定速率(fixedRate)等来指定任务执行的时间规则。

步骤

创建定时任务类

创建一个 Spring 管理的Bean类,在类中定义需要定时执行的方法。例如:

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

@Component
public class SimpleScheduledTask {
    @Scheduled(fixedRate = 5000) // 每隔5秒执行一次任务
    public void executeTask() {
        System.out.println("定时任务执行了");
    }
}

配置定时任务(在 Spring Boot 主类或配置类中开启定时任务支持) 

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

@SpringBootApplication
@EnableScheduling
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

时间参数设置方式 

cron表达式 

cron表达式是一种强大的时间表达式,用于精确地指定任务执行时间。例如,@Scheduled(cron = "0 0 12 * * *")表示每天中午 12 点执行任务。cron表达式的格式为:秒 分 时 日 月 周 年(年可省略),每个字段可以使用通配符(*)、数字范围(如1 - 5)、逗号分隔的列表(如1,3,5)等来表示不同的时间规则。

 固定延迟(fixedDelay)

fixedDelay参数指定的时间间隔是在前一个任务执行完成后,等待指定的时间再执行下一个任务。例如,@Scheduled(fixedDelay = 3000)表示当前任务执行完成后,等待 3 秒再执行下一个任务。

固定速率(fixedRate 

fixedRate参数指定的时间间隔是从任务开始执行的时间点开始计算,每隔固定的时间就执行一次任务,不管前一个任务是否已经执行完成。例如,@Scheduled(fixedRate = 2000)表示每隔 2 秒就执行一次任务,即使前一个任务还没有执行完。

扩展 

使用ScheduledExecutorService实现复杂定时任务(更灵活的方式) 

原理 

ScheduledExecutorService是 Java 提供的用于在后台执行定时任务或周期性任务的接口,它提供了更灵活的任务调度方式,相比@Scheduled注解可以更好地控制任务的并发执行、动态任务添加和删除等操作。Spring Boot 也可以很方便地整合ScheduledExecutorService来实现定时任务。

步骤 

创建定时任务类 
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.springframework.stereotype.Component;

@Component
public class ComplexScheduledTask {
    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

    public void startTask() {
        // 延迟1秒后执行任务,之后每隔3秒执行一次
        scheduler.scheduleAtFixedRate(() -> {
            System.out.println("复杂定时任务执行了");
        }, 1, 3, TimeUnit.SECONDS);
    }
}
在合适的地方启动定时任务(如在应用启动后) 

可以在 Spring Boot 的启动类的main方法中,或者通过实现ApplicationListener<ContextRefreshedEvent>接口在容器初始化完成后启动定时任务。以下是通过ApplicationListener接口启动定时任务的示例:

import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;

@Component
public class TaskStarter implements ApplicationListener<ContextRefreshedEvent> {
    private final ComplexScheduledTask complexScheduledTask;

    public TaskStarter(ComplexScheduledTask complexScheduledTask) {
        this.complexScheduledTask = complexScheduledTask;
    }

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        if (event.getApplicationContext().getParent() == null) {
            // 这是根容器,在这里启动定时任务
            complexScheduledTask.startTask();
        }
    }
}

优势和注意事项 

 优势

并发控制更好:可以通过ScheduledExecutorService的线程池配置来控制任务的并发执行,例如设置线程池大小,适用于有多个定时任务需要并发执行或者对任务执行的顺序和并发情况有特殊要求的场景。

动态任务管理:能够方便地在运行时动态添加、删除或修改定时任务,例如根据用户的配置或者系统的运行状态来调整定时任务的执行计划,这在一些复杂的业务场景中非常有用。

注意事项 

需要注意资源管理,特别是在长时间运行的定时任务中,要确保任务不会出现内存泄漏、资源占用过多等问题。例如,在任务执行过程中如果涉及到数据库连接、文件操作等资源的使用,需要合理地进行资源的获取和释放。

结合消息队列实现分布式定时任务(适用于分布式系统) 

原理 

在分布式系统中,单纯的本地定时任务可能无法满足需求。通过结合消息队列(如 RabbitMQ、Kafka 等)可以实现分布式定时任务。基本思路是将定时任务的触发信息(如任务执行时间、任务参数等)发送到消息队列中,然后多个消费者(可以是不同的服务实例)从消息队列中获取任务信息并执行任务,这样可以实现任务的分布式调度和执行。

步骤(以 RabbitMQ 为例)

 配置消息队列和生产者(用于发送定时任务消息)

首先需要在 Spring Boot 项目中配置 RabbitMQ,包括添加依赖、配置连接信息等。然后创建一个生产者来发送定时任务相关的消息。例如:

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class DistributedTaskProducer {
    private final RabbitTemplate rabbitTemplate;

    @Autowired
    public DistributedTaskProducer(RabbitTemplate rabbitTemplate) {
        this.rabbitTemplate = rabbitTemplate;
    }

    @Scheduled(cron = "0 0 12 * * *") // 每天中午12点发送定时任务消息
    public void sendTaskMessage() {
        String taskMessage = "这是一个分布式定时任务消息";
        rabbitTemplate.convertAndSend("task_exchange", "task_routing_key", taskMessage);
    }
}
配置消费者(用于接收并执行定时任务)

配置消费者来接收消息队列中的任务消息并执行任务逻辑。例如:

import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class DistributedTaskConsumer {
    @RabbitListener(queues = "task_queue")
    public void receiveAndExecuteTask(String taskMessage) {
        System.out.println("接收到分布式定时任务消息并执行:" + taskMessage);
        // 在这里添加具体的任务执行逻辑,如调用其他服务、处理数据等
    }
}

 注意事项

需要确保消息队列的可靠性和稳定性,包括消息的持久化、消息的确认机制等,以防止任务消息丢失或者重复执行。例如,在 RabbitMQ 中,可以通过设置消息的持久化属性和合理使用消息确认机制(如手动确认消息)来提高系统的可靠性。

分布式定时任务的调度和执行可能会涉及到时间同步、任务幂等性等问题。例如,不同的服务实例的时间可能存在差异,需要考虑这种差异对定时任务执行时间的影响;同时,为了防止任务在分布式环境下因为网络等原因重复执行,需要在任务执行逻辑中添加幂等性处理机制,如通过记录任务执行状态等方式来确保任务只执行一次。

;