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

开发 前端
在本文中,我们通过Yaml​属性配置了自定义线程池,详细介绍了@Scheduled注解的多种用法,并实现了一个能够优雅地启动和停止定时任务的管理系统。

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

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

运行效果:

图片图片

图片图片

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

项目结构

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

  1. Spring Boot主应用程序:启动Spring Boot应用,并注册定时任务。
  2. 定时任务实现:定义定时任务的逻辑。
  3. 任务管理器:提供控制定时任务启停的方法。
  4. 前端页面:提供简洁的前端页面,允许用户通过页面来启停定时任务。

项目依赖配置(pom.xml)

首先,我们需要在pom.xml文件中添加相关的依赖:

<?xml versinotallow="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秒。

  1. 固定延迟执行(fixedDelay):

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

  1. 首次延迟执行(initialDelay):

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

  1. 使用 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注解的多种用法,并实现了一个能够优雅地启动和停止定时任务的管理系统。通过这种方式,我们可以灵活地控制应用中的定时任务,提高系统的稳定性和可维护性。这种设计非常适合在生产环境中应用,尤其是在需要频繁调整任务调度策略的场景下。

责任编辑:武晓燕 来源: 路条编程
相关推荐

2024-07-31 14:03:00

Spring定时任务管理

2024-09-04 11:16:44

端口Spring配置类

2024-08-30 11:28:09

2024-09-02 08:17:18

2024-09-20 05:49:04

SpringBoot后端

2019-02-20 15:52:50

技术开发代码

2024-02-28 09:54:07

线程池配置

2017-08-16 16:41:04

JavaSpringBoot定时任务

2024-09-06 10:05:47

SpELSpring权限

2012-02-07 13:31:14

SpringJava

2009-10-28 10:05:29

Ubuntucrontab定时任务

2021-04-11 07:48:42

定时任务Linux jdk

2023-01-04 09:23:58

2024-09-09 08:11:12

2010-03-10 15:47:58

crontab定时任务

2023-10-31 12:42:00

Spring动态增删启停

2021-06-30 07:19:34

SpringBoot定时任务

2024-11-04 16:01:01

2020-12-21 07:31:23

实现单机JDK

2010-01-07 13:38:41

Linux定时任务
点赞
收藏

51CTO技术栈公众号