查看原文
其他

SpringBoot3.3 优雅停止/重启定时任务功能太赞了!

编程疏影 路条编程
2024-09-04


SpringBoot3.3 优雅停止/重启定时任务功能太赞了!

在现代Java应用开发中,定时任务是非常常见的功能。无论是定期备份、数据清理,还是定期发送通知,定时任务都能发挥至关重要的作用。然而,在一个复杂的应用程序中,如何优雅地管理这些定时任务的启动与停止,尤其是在不影响系统正常运行的情况下,显得尤为重要。

Spring Boot 提供了强大的任务调度支持,通过@Scheduled注解可以轻松地创建定时任务,并且可以通过配置来灵活地管理这些任务的执行环境。在本文中,我们将深入探讨如何通过Yaml属性配置自定义线程池,并详细介绍如何使用@Scheduled注解实现多样化的定时任务。此外,我们还会探讨如何优雅地停止和重启这些任务,确保系统的稳定性和任务的灵活性。

运行效果:

若想获取项目完整代码以及其他文章的项目源码,且在代码编写时遇到问题需要咨询交流,欢迎加入下方的知识星球。

项目结构

为了实现我们的目标,我们的项目结构将包含以下部分:

  1. Spring Boot主应用程序:启动Spring Boot应用,并注册定时任务。

  2. 定时任务实现:定义定时任务的逻辑。

  3. 任务管理器:提供控制定时任务启停的方法。

  4. 前端页面:提供简洁的前端页面,允许用户通过页面来启停定时任务。

项目依赖配置(pom.xml)

首先,我们需要在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 https://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>3.3.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.icoderoad</groupId>
<artifactId>taskmanager</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>taskmanager</name>
<description>Demo project for Spring Boot</description>

<properties>
<java.version>17</java.version>
</properties>
<dependencies>

<!-- Thymeleaf template engine -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

<!-- Spring Boot Web Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- Lombok for reducing boilerplate code -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

配置文件(application.yaml)

在任务调度中,线程池的配置是影响任务执行效率和可靠性的重要因素。默认情况下,Spring Boot 为调度任务提供了一个单线程的执行器,但对于复杂的业务场景,我们往往需要一个更高效的多线程池来管理多个任务的并发执行。通过在application.yaml中配置ThreadPoolTaskScheduler,我们可以自定义线程池的大小以及关闭策略。

接下来,在src/main/resources/application.yaml中添加以下配置:

server:
port: 8080

spring:
task:
scheduling:
pool:
size: 5 # 配置线程池大小,设为5个线程
shutdown:
await-termination: true # 在应用关闭时等待所有任务完成
await-termination-period: 30s # 等待时间设置为30秒

这里我们设置了一个调度池,并配置了任务关闭时的等待策略,以确保任务能够优雅停止。

创建 TaskSchedulerProperties 配置类

创建一个配置类来绑定 application.yml 中的属性:

package com.icoderoad.taskmanager.properties;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import lombok.Data;

@Data
@Component
@ConfigurationProperties(prefix = "spring.task.scheduling.pool")
public class TaskSchedulerProperties {

private int size;

}

定时任务的实现

@Scheduled 注解的详细用法

@Scheduled注解是Spring中的一个强大工具,用于定义各种类型的定时任务。它支持多种触发方式,包括固定速率(fixedRate)、固定延迟(fixedDelay)、和Cron表达式等,能够满足几乎所有常见的定时任务需求。

  1. 固定速率执行(fixedRate):

  • @Scheduled(fixedRate = 5000) 表示任务将在上一次任务开始执行后,5秒钟再执行下一次任务。即任务之间的间隔时间固定为5秒。

  • 固定延迟执行(fixedDelay):

    • @Scheduled(fixedDelay = 5000) 表示任务将在上一次任务执行结束后,等待5秒钟再执行下一次任务。与fixedRate不同的是,fixedDelay计算的是任务结束与下一次任务开始之间的间隔。

  • 首次延迟执行(initialDelay):

    • @Scheduled(initialDelay = 10000, fixedRate = 5000) 表示任务将在应用启动后10秒钟开始第一次执行,随后每隔5秒执行一次。

  • 使用 Cron 表达式:

    • @Scheduled(cron = "0 0/1 * * * ?") 表示任务将在每分钟的第0秒执行一次。Cron表达式是一种非常灵活的时间表达方式,允许指定任务的精确执行时间,例如每小时的整点、每月的特定日期等。

    通过以上的配置与注解使用方法,我们可以灵活地在Spring Boot应用中创建各种类型的定时任务,同时结合自定义线程池配置,确保任务的并发执行效率和系统的稳定性。

    src/main/java/com/icoderoad/taskmanager/task/目录下创建MyScheduledTask.java类,用于定义我们的定时任务。

    package com.icoderoad.taskmanager.task;

    import java.time.LocalDateTime;
    import java.time.format.DateTimeFormatter;

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;

    @Component
    public class ScheduledTask {

    private static final Logger logger = LoggerFactory.getLogger(ScheduledTask.class);
    private static final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    public void scheduleTaskWithFixedRate() {
    logger.info("固定速率任务执行时间: {}", dateTimeFormatter.format(LocalDateTime.now()));
    }

    public void scheduleTaskWithFixedDelay() {
    logger.info("固定延迟任务执行时间: {}", dateTimeFormatter.format(LocalDateTime.now()));
    }

    public void scheduleTaskWithInitialDelay() {
    logger.info("首次延迟任务执行时间: {}", dateTimeFormatter.format(LocalDateTime.now()));
    }

    public void scheduleTaskWithCronExpression() {
    logger.info("Cron表达式任务执行时间: {}", dateTimeFormatter.format(LocalDateTime.now()));
    }
    }

    在这个简单的示例中,performTask()方法会每隔5秒钟执行一次,并输出当前的时间戳。

    任务管理器的实现

    为了控制定时任务的启动和停止,我们可以创建一个任务管理器类。

    src/main/java/com/icoderoad/taskmanager/service/目录下创建TaskSchedulerManager.java类:


    此类提供了停止和重启任务的功能。

    控制器的实现

    src/main/java/com/icoderoad/taskmanager/controller/目录下创建TaskController.java类:

    package com.icoderoad.taskmanager.service;

    import java.util.Date;
    import java.util.concurrent.ScheduledFuture;

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.scheduling.TaskScheduler;
    import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
    import org.springframework.scheduling.support.CronTrigger;
    import org.springframework.stereotype.Service;

    import com.icoderoad.taskmanager.properties.TaskSchedulerProperties;
    import com.icoderoad.taskmanager.task.ScheduledTask;

    @Service
    public class TaskSchedulerManager {

    private final TaskScheduler taskScheduler;
    private final TaskSchedulerProperties taskSchedulerProperties;
    private ScheduledFuture<?> fixedDelayTask;
    private ScheduledFuture<?> initialDelayTask;
    private ScheduledFuture<?> cronExpressionTask;
    private boolean isTaskRunning = false;

    @Autowired
    private ScheduledTask scheduledTaskBean;

    @Autowired
    public TaskSchedulerManager(TaskScheduler taskScheduler, TaskSchedulerProperties taskSchedulerProperties) {
    this.taskScheduler = taskScheduler;
    this.taskSchedulerProperties = taskSchedulerProperties;
    initializeScheduler();
    }

    private void initializeScheduler() {
    if (taskScheduler instanceof ThreadPoolTaskScheduler) {
    ((ThreadPoolTaskScheduler) taskScheduler).setPoolSize(taskSchedulerProperties.getSize());
    }
    }

    public void startTask() {
    if (!isTaskRunning) {
    fixedDelayTask = taskScheduler.scheduleWithFixedDelay(scheduledTaskBean::scheduleTaskWithFixedDelay, 5000);
    initialDelayTask = taskScheduler.schedule(() -> {
    scheduledTaskBean.scheduleTaskWithInitialDelay();
    }, new Date(System.currentTimeMillis() + 10000)); // 初始延迟任务,10秒后执行
    cronExpressionTask = taskScheduler.schedule(scheduledTaskBean::scheduleTaskWithCronExpression,
    new CronTrigger("0 0/1 * * * ?")); // Cron表达式任务
    isTaskRunning = true;
    }
    }

    public void stopTask() {
    if (isTaskRunning) {
    if (fixedDelayTask != null) {
    fixedDelayTask.cancel(true);
    }
    if (initialDelayTask != null) {
    initialDelayTask.cancel(true);
    }
    if (cronExpressionTask != null) {
    cronExpressionTask.cancel(true);
    }
    isTaskRunning = false;
    }
    }

    public boolean isTaskRunning() {
    return isTaskRunning;
    }
    }

    通过这个控制器,我们能够处理来自前端页面的请求,停止或重启定时任务。

    启动应用程序

    src/main/java/com/icoderoad/taskmanager/目录下创建TaskManagerApplication.java`类:

    package com.icoderoad.taskmanager;

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

    @SpringBootApplication
    @EnableScheduling
    public class TaskmanagerApplication {

    public static void main(String[] args) {
    SpringApplication.run(TaskmanagerApplication.class, args);
    }

    }

    在启动类中,我们启用了定时任务调度功能。

    视图控制器

    package com.icoderoad.taskmanager.controller;

    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;

    @Controller
    public class IndexController {

    @GetMapping("/")
    public String index() {
    return "index";
    }
    }

    前端页面(Thymeleaf + Bootstrap)

    src/main/resources/templates/目录下创建一个简单的HTML文件index.html

    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org">
    <head>
    <meta charset="UTF-8">
    <title>任务管理</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    </head>
    <body>
    <div class="container">
    <h1 class="mt-5">定时任务管理</h1>
    <div class="mt-3">
    <button id="startBtn" class="btn btn-danger">启动任务</button>
    <button id="stopBtn" class="btn btn-secondary" disabled>停止任务</button>
    </div>
    <div id="messageBox" class="alert mt-3" role="alert" style="display:none;"></div>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
    <script>
    $(document).ready(function() {
    function showMessage(type, message) {
    const messageBox = $('#messageBox');
    messageBox.removeClass('alert-success alert-danger').addClass('alert-' + type);
    messageBox.text(message);
    messageBox.show();
    }

    function updateButtonStates(isRunning) {
    if (isRunning) {
    $('#startBtn').removeClass('btn-danger').addClass('btn-secondary').prop('disabled', true);
    $('#stopBtn').removeClass('btn-secondary').addClass('btn-danger').prop('disabled', false);
    } else {
    $('#startBtn').removeClass('btn-secondary').addClass('btn-danger').prop('disabled', false);
    $('#stopBtn').removeClass('btn-danger').addClass('btn-secondary').prop('disabled', true);
    }
    }

    // 页面加载时获取任务状态并更新按钮状态
    $.get("/task/status", function(response) {
    updateButtonStates(response.isRunning);
    });

    $('#startBtn').click(function() {
    $.post("/task/start-task", function(response) {
    if (response.status === 'success') {
    showMessage('success', response.message);
    updateButtonStates(true);
    } else {
    showMessage('danger', response.message);
    }
    });
    });

    $('#stopBtn').click(function() {
    $.post("/task/stop-task", function(response) {
    if (response.status === 'success') {
    showMessage('success', response.message);
    updateButtonStates(false);
    } else {
    showMessage('danger', response.message);
    }
    });
    });
    });
    </script>
    </body>
    </html>

    此页面提供了简单的按钮,允许用户停止或重启定时任务。

    运行与测试

    启动应用程序后,打开浏览器访问http://localhost:8080/taskManager,你将看到管理定时任务的界面。点击“停止任务”按钮,可以停止定时任务;点击“重启任务”按钮,则可以重新启动定时任务。

    总结

    在本文中,我们通过Yaml属性配置了自定义线程池,详细介绍了@Scheduled注解的多种用法,并实现了一个能够优雅地启动和停止定时任务的管理系统。通过这种方式,我们可以灵活地控制应用中的定时任务,提高系统的稳定性和可维护性。这种设计非常适合在生产环境中应用,尤其是在需要频繁调整任务调度策略的场景下。


    今天就讲到这里,如果有问题需要咨询,大家可以直接留言或扫下方二维码来知识星球找我,我们会尽力为你解答。


    AI资源聚合站已经正式上线,该平台不仅仅是一个AI资源聚合站,更是一个为追求知识深度和广度的人们打造的智慧聚集地。通过访问 AI 资源聚合网站 https://ai-ziyuan.techwisdom.cn/,你将进入一个全方位涵盖人工智能和语言模型领域的宝藏库。


    作者:路条编程(转载请获本公众号授权,并注明作者与出处)


    继续滑动看下一个
    路条编程
    向上滑动看下一个

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存