
在传统的spring boot api设计中,如果一个@postmapping或@getmapping方法内部包含了耗时较长的业务逻辑(例如,复杂的计算、大量数据处理、调用外部慢速服务等),并且这些逻辑是同步执行的,那么会带来以下问题:
原始代码示例中,一个简单的for循环在API方法内部执行,这正是上述问题的典型体现。当多个带有不同timeToRun参数的请求同时到达时,如果用户希望取消其中一些请求的执行,现有的同步模型无法提供支持。
要解决上述问题,核心思想是将耗时操作从主请求线程中分离出来,进行异步执行,并提供一个机制来追踪这些异步任务,以便在需要时进行取消。
具体实现步骤包括:
Spring Boot通过@EnableAsync注解和TaskExecutor来支持异步方法。为了更好地控制线程池行为,我们通常会自定义一个ThreadPoolTaskExecutor。
// src/main/java/com/example/async/AsyncConfig.java
package com.example.async;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean(name = "taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5); // 核心线程数
executor.setMaxPoolSize(10); // 最大线程数
executor.setQueueCapacity(25); // 队列容量
executor.setThreadNamePrefix("AsyncTask-"); // 线程名前缀
executor.initialize();
return executor;
}
}创建一个服务类,其中包含实际执行耗时逻辑的方法。该方法需要用@Async注解标记,并指定使用的TaskExecutor。为了支持取消,该方法应返回一个CompletableFuture,并且在内部逻辑中周期性地检查线程中断状态。
// src/main/java/com/example/async/LongRunningService.java
package com.example.async;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.concurrent.CompletableFuture;
@Service
public class LongRunningService {
private static final Logger logger = LoggerFactory.getLogger(LongRunningService.class);
@Async("taskExecutor") // 指定使用名为"taskExecutor"的线程池
public CompletableFuture<String> executeLongRunningTask(String taskId, int timeToRun) {
logger.info("Task {} started with timeToRun: {}", taskId, timeToRun);
try {
for (int i = 0; i < timeToRun; i++) {
// 模拟耗时操作
Thread.sleep(1000); // 每次操作耗时1秒
// 检查线程是否被中断
if (Thread.currentThread().isInterrupted()) {
logger.warn("Task {} was interrupted after {} seconds.", taskId, i + 1);
return CompletableFuture.completedFuture("Task " + taskId + " cancelled.");
}
logger.info("Task {} processing... {}/{} seconds", taskId, i + 1, timeToRun);
}
logger.info("Task {} completed successfully.", taskId);
return CompletableFuture.completedFuture("Task " + taskId + " completed.");
} catch (InterruptedException e) {
// 捕获InterruptedException,通常表示线程被中断
Thread.currentThread().interrupt(); // 重新设置中断标志
logger.warn("Task {} caught InterruptedException. It was cancelled.", taskId);
return CompletableFuture.completedFuture("Task " + taskId + " cancelled due to interruption.");
} catch (Exception e) {
logger.error("Task {} encountered an error: {}", taskId, e.getMessage());
return CompletableFuture.completedFuture("Task " + taskId + " failed: " + e.getMessage());
}
}
}关键点:
我们需要一个控制器来接收启动任务和取消任务的请求。为了管理任务,我们将使用一个Map来存储任务ID和对应的CompletableFuture。
// src/main/java/com/example/async/TaskController.java
package com.example.async;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CompletableFuture;
@RestController
@RequestMapping("/api/tasks")
public class TaskController {
private static final Logger logger = LoggerFactory.getLogger(TaskController.class);
private final LongRunningService longRunningService;
// 使用ConcurrentHashMap来安全地存储任务Future
private final Map<String, CompletableFuture<String>> runningTasks = new ConcurrentHashMap<>();
public TaskController(LongRunningService longRunningService) {
this.longRunningService = longRunningService;
}
/**
* 启动一个长耗时任务
* @param timeToRun 模拟任务运行时间(秒)
* @return 包含任务ID的响应
*/
@PostMapping("/start/{timeToRun}")
public ResponseEntity<String> startTask(@PathVariable int timeToRun) {
String taskId = UUID.randomUUID().toString();
logger.info("Received request to start task {} with timeToRun: {}", taskId, timeToRun);
CompletableFuture<String> future = longRunningService.executeLongRunningTask(taskId, timeToRun);
runningTasks.put(taskId, future);
// 异步监听任务完成,并从map中移除
future.whenComplete((result, ex) -> {
if (ex == null) {
logger.info("Task {} completed with result: {}", taskId, result);
} else {
logger.error("Task {} failed with exception: {}", taskId, ex.getMessage());
}
runningTasks.remove(taskId); // 任务完成后从map中移除
});
return ResponseEntity.ok("Task started. Task ID: " + taskId);
}
/**
* 取消一个正在运行的任务
* @param taskId 要取消的任务ID
* @return 取消结果
*/
@PostMapping("/cancel/{taskId}")
public ResponseEntity<String> cancelTask(@PathVariable String taskId) {
CompletableFuture<String> future = runningTasks.get(taskId);
if (future == null) {
logger.warn("Attempted to cancel non-existent or already completed task: {}", taskId);
return ResponseEntity.badRequest().body("Task " + taskId + " not found or already completed.");
}
if (future.isDone()) {
logger.info("Task {} is already done, cannot cancel.", taskId);
runningTasks.remove(taskId); // 确保已完成的任务被移除
return ResponseEntity.badRequest().body("Task " + taskId + " is already done, cannot cancel.");
}
// 尝试取消任务,true表示如果任务正在运行,会尝试中断线程
boolean cancelled = future.cancel(true);
if (cancelled) {
logger.info("Task {} successfully requested for cancellation.", taskId);
// 任务被成功取消后,也从map中移除
runningTasks.remove(taskId);
return ResponseEntity.ok("Task " + taskId + " cancellation requested.");
} else {
logger.warn("Task {} could not be cancelled. It might be completed or not interruptible.", taskId);
return ResponseEntity.status(500).body("Failed to cancel task " + taskId + ".");
}
}
/**
* 获取所有正在运行的任务ID
* @return 正在运行的任务ID列表
*/
@GetMapping("/status")
public ResponseEntity<Map<String, String>> getRunningTasks() {
Map<String, String> statusMap = new ConcurrentHashMap<>();
runningTasks.forEach((id, future) -> {
statusMap.put(id, future.isDone() ? "DONE" : (future.isCancelled() ? "CANCELLED" : "RUNNING"));
});
return ResponseEntity.ok(statusMap);
}
}通过将长耗时操作异步化并结合任务管理机制,我们能够在Spring Boot中实现对API请求的动态取消。这种模式不仅提升了API的响应性和用户体验,也增强了系统的资源利用效率和稳定性。理解异步编程的核心概念、正确配置线程池以及在任务内部响应中断是实现这一功能的关键。在实际项目中,还需要根据具体需求,进一步考虑任务持久化、实时状态反馈和安全性等高级特性。
以上就是在Spring Boot中实现长耗时API请求的异步执行与取消策略的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号