SpringBoot3.3 优雅停止/重启定时任务功能太赞了!
SpringBoot3.3 优雅停止/重启定时任务功能太赞了!
在现代Java应用开发中,定时任务是非常常见的功能。无论是定期备份、数据清理,还是定期发送通知,定时任务都能发挥至关重要的作用。然而,在一个复杂的应用程序中,如何优雅地管理这些定时任务的启动与停止,尤其是在不影响系统正常运行的情况下,显得尤为重要。
Spring Boot 提供了强大的任务调度支持,通过@Scheduled
注解可以轻松地创建定时任务,并且可以通过配置来灵活地管理这些任务的执行环境。在本文中,我们将深入探讨如何通过Yaml
属性配置自定义线程池,并详细介绍如何使用@Scheduled
注解实现多样化的定时任务。此外,我们还会探讨如何优雅地停止和重启这些任务,确保系统的稳定性和任务的灵活性。
运行效果:
若想获取项目完整代码以及其他文章的项目源码,且在代码编写时遇到问题需要咨询交流,欢迎加入下方的知识星球。
项目结构
为了实现我们的目标,我们的项目结构将包含以下部分:
Spring Boot主应用程序:启动Spring Boot应用,并注册定时任务。
定时任务实现:定义定时任务的逻辑。
任务管理器:提供控制定时任务启停的方法。
前端页面:提供简洁的前端页面,允许用户通过页面来启停定时任务。
项目依赖配置(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表达式等,能够满足几乎所有常见的定时任务需求。
固定速率执行(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
注解的多种用法,并实现了一个能够优雅地启动和停止定时任务的管理系统。通过这种方式,我们可以灵活地控制应用中的定时任务,提高系统的稳定性和可维护性。这种设计非常适合在生产环境中应用,尤其是在需要频繁调整任务调度策略的场景下。