SpringBoot3.3 優(yōu)雅停止/重啟定時任務(wù)功能太贊了!
在現(xiàn)代Java應(yīng)用開發(fā)中,定時任務(wù)是非常常見的功能。無論是定期備份、數(shù)據(jù)清理,還是定期發(fā)送通知,定時任務(wù)都能發(fā)揮至關(guān)重要的作用。然而,在一個復(fù)雜的應(yīng)用程序中,如何優(yōu)雅地管理這些定時任務(wù)的啟動與停止,尤其是在不影響系統(tǒng)正常運(yùn)行的情況下,顯得尤為重要。
Spring Boot 提供了強(qiáng)大的任務(wù)調(diào)度支持,通過@Scheduled注解可以輕松地創(chuàng)建定時任務(wù),并且可以通過配置來靈活地管理這些任務(wù)的執(zhí)行環(huán)境。在本文中,我們將深入探討如何通過Yaml屬性配置自定義線程池,并詳細(xì)介紹如何使用@Scheduled注解實(shí)現(xiàn)多樣化的定時任務(wù)。此外,我們還會探討如何優(yōu)雅地停止和重啟這些任務(wù),確保系統(tǒng)的穩(wěn)定性和任務(wù)的靈活性。
運(yùn)行效果:
圖片
圖片
若想獲取項(xiàng)目完整代碼以及其他文章的項(xiàng)目源碼,且在代碼編寫時遇到問題需要咨詢交流,歡迎加入下方的知識星球。
項(xiàng)目結(jié)構(gòu)
為了實(shí)現(xiàn)我們的目標(biāo),我們的項(xiàng)目結(jié)構(gòu)將包含以下部分:
- Spring Boot主應(yīng)用程序:啟動Spring Boot應(yīng)用,并注冊定時任務(wù)。
- 定時任務(wù)實(shí)現(xiàn):定義定時任務(wù)的邏輯。
- 任務(wù)管理器:提供控制定時任務(wù)啟停的方法。
- 前端頁面:提供簡潔的前端頁面,允許用戶通過頁面來啟停定時任務(wù)。
項(xiàng)目依賴配置(pom.xml)
首先,我們需要在pom.xml文件中添加相關(guān)的依賴:
<?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)
在任務(wù)調(diào)度中,線程池的配置是影響任務(wù)執(zhí)行效率和可靠性的重要因素。默認(rèn)情況下,Spring Boot 為調(diào)度任務(wù)提供了一個單線程的執(zhí)行器,但對于復(fù)雜的業(yè)務(wù)場景,我們往往需要一個更高效的多線程池來管理多個任務(wù)的并發(fā)執(zhí)行。通過在application.yaml中配置ThreadPoolTaskScheduler,我們可以自定義線程池的大小以及關(guān)閉策略。
接下來,在src/main/resources/application.yaml中添加以下配置:
server:
port: 8080
spring:
task:
scheduling:
pool:
size: 5 # 配置線程池大小,設(shè)為5個線程
shutdown:
await-termination: true # 在應(yīng)用關(guān)閉時等待所有任務(wù)完成
await-termination-period: 30s # 等待時間設(shè)置為30秒
這里我們設(shè)置了一個調(diào)度池,并配置了任務(wù)關(guān)閉時的等待策略,以確保任務(wù)能夠優(yōu)雅停止。
創(chuàng)建 TaskSchedulerProperties 配置類
創(chuàng)建一個配置類來綁定 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;
}
定時任務(wù)的實(shí)現(xiàn)
@Scheduled 注解的詳細(xì)用法
@Scheduled注解是Spring中的一個強(qiáng)大工具,用于定義各種類型的定時任務(wù)。它支持多種觸發(fā)方式,包括固定速率(fixedRate)、固定延遲(fixedDelay)、和Cron表達(dá)式等,能夠滿足幾乎所有常見的定時任務(wù)需求。
- 固定速率執(zhí)行(fixedRate):
@Scheduled(fixedRate = 5000) 表示任務(wù)將在上一次任務(wù)開始執(zhí)行后,5秒鐘再執(zhí)行下一次任務(wù)。即任務(wù)之間的間隔時間固定為5秒。
- 固定延遲執(zhí)行(fixedDelay):
@Scheduled(fixedDelay = 5000) 表示任務(wù)將在上一次任務(wù)執(zhí)行結(jié)束后,等待5秒鐘再執(zhí)行下一次任務(wù)。與fixedRate不同的是,fixedDelay計算的是任務(wù)結(jié)束與下一次任務(wù)開始之間的間隔。
首次延遲執(zhí)行(initialDelay):
@Scheduled(initialDelay = 10000, fixedRate = 5000) 表示任務(wù)將在應(yīng)用啟動后10秒鐘開始第一次執(zhí)行,隨后每隔5秒執(zhí)行一次。
使用 Cron 表達(dá)式:
@Scheduled(cron = "0 0/1 * * * ?") 表示任務(wù)將在每分鐘的第0秒執(zhí)行一次。Cron表達(dá)式是一種非常靈活的時間表達(dá)方式,允許指定任務(wù)的精確執(zhí)行時間,例如每小時的整點(diǎn)、每月的特定日期等。
通過以上的配置與注解使用方法,我們可以靈活地在Spring Boot應(yīng)用中創(chuàng)建各種類型的定時任務(wù),同時結(jié)合自定義線程池配置,確保任務(wù)的并發(fā)執(zhí)行效率和系統(tǒng)的穩(wěn)定性。
在src/main/java/com/icoderoad/taskmanager/task/目錄下創(chuàng)建MyScheduledTask.java類,用于定義我們的定時任務(wù)。
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("固定速率任務(wù)執(zhí)行時間: {}", dateTimeFormatter.format(LocalDateTime.now()));
}
public void scheduleTaskWithFixedDelay() {
logger.info("固定延遲任務(wù)執(zhí)行時間: {}", dateTimeFormatter.format(LocalDateTime.now()));
}
public void scheduleTaskWithInitialDelay() {
logger.info("首次延遲任務(wù)執(zhí)行時間: {}", dateTimeFormatter.format(LocalDateTime.now()));
}
public void scheduleTaskWithCronExpression() {
logger.info("Cron表達(dá)式任務(wù)執(zhí)行時間: {}", dateTimeFormatter.format(LocalDateTime.now()));
}
}
在這個簡單的示例中,performTask()方法會每隔5秒鐘執(zhí)行一次,并輸出當(dāng)前的時間戳。
任務(wù)管理器的實(shí)現(xiàn)
為了控制定時任務(wù)的啟動和停止,我們可以創(chuàng)建一個任務(wù)管理器類。
在src/main/java/com/icoderoad/taskmanager/service/目錄下創(chuàng)建TaskSchedulerManager.java類:
此類提供了停止和重啟任務(wù)的功能。
控制器的實(shí)現(xiàn)
在src/main/java/com/icoderoad/taskmanager/controller/目錄下創(chuàng)建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)); // 初始延遲任務(wù),10秒后執(zhí)行
cronExpressionTask = taskScheduler.schedule(scheduledTaskBean::scheduleTaskWithCronExpression,
new CronTrigger("0 0/1 * * * ?")); // Cron表達(dá)式任務(wù)
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;
}
}
通過這個控制器,我們能夠處理來自前端頁面的請求,停止或重啟定時任務(wù)。
啟動應(yīng)用程序
在src/main/java/com/icoderoad/taskmanager/目錄下創(chuàng)建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);
}
}
在啟動類中,我們啟用了定時任務(wù)調(diào)度功能。
視圖控制器
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/目錄下創(chuàng)建一個簡單的HTML文件index.html:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>任務(wù)管理</title>
<link rel="stylesheet">
</head>
<body>
<div class="container">
<h1 class="mt-5">定時任務(wù)管理</h1>
<div class="mt-3">
<button id="startBtn" class="btn btn-danger">啟動任務(wù)</button>
<button id="stopBtn" class="btn btn-secondary" disabled>停止任務(wù)</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);
}
}
// 頁面加載時獲取任務(wù)狀態(tài)并更新按鈕狀態(tài)
$.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>
此頁面提供了簡單的按鈕,允許用戶停止或重啟定時任務(wù)。
運(yùn)行與測試
啟動應(yīng)用程序后,打開瀏覽器訪問http://localhost:8080/taskManager,你將看到管理定時任務(wù)的界面。點(diǎn)擊“停止任務(wù)”按鈕,可以停止定時任務(wù);點(diǎn)擊“重啟任務(wù)”按鈕,則可以重新啟動定時任務(wù)。
總結(jié)
在本文中,我們通過Yaml屬性配置了自定義線程池,詳細(xì)介紹了@Scheduled注解的多種用法,并實(shí)現(xiàn)了一個能夠優(yōu)雅地啟動和停止定時任務(wù)的管理系統(tǒng)。通過這種方式,我們可以靈活地控制應(yīng)用中的定時任務(wù),提高系統(tǒng)的穩(wěn)定性和可維護(hù)性。這種設(shè)計非常適合在生產(chǎn)環(huán)境中應(yīng)用,尤其是在需要頻繁調(diào)整任務(wù)調(diào)度策略的場景下。