Spring Boot如何在单个控制器处理外部API即时响应与回调响应
在同一控制器中获取外部API的即时响应与回调响应的实现方案
核心思路
要在同一个控制器方法中同时拿到外部API的即时响应和后续回调响应,关键是通过请求关联标识将两者绑定,再利用异步等待机制让主请求等待回调结果返回。常用的实现方式是基于内存存储(如ConcurrentHashMap)维护请求的异步任务,配合回调接口完成结果传递。
具体实现方案
1. 定义全局关联存储与工具类
先创建一个用于存储回调任务的全局容器,用来关联请求ID和对应的异步Future:
@Component public class CallbackTaskHolder { // 用ConcurrentHashMap保证线程安全,key为请求关联ID,value为待完成的CompletableFuture private final ConcurrentHashMap<String, CompletableFuture<CallbackResponse>> taskMap = new ConcurrentHashMap<>(); // 注册回调任务 public void registerTask(String requestId, CompletableFuture<CallbackResponse> future) { taskMap.put(requestId, future); } // 完成回调任务并移除 public void completeTask(String requestId, CallbackResponse response) { CompletableFuture<CallbackResponse> future = taskMap.remove(requestId); if (future != null) { future.complete(response); } } // 定时清理超时任务(避免内存泄漏) @Scheduled(fixedRate = 60000) // 每分钟清理一次已完成/超时的任务 public void cleanTimeoutTasks() { taskMap.entrySet().removeIf(entry -> entry.getValue().isDone() || entry.getValue().isCancelled()); } }
2. 修改主控制器方法
在MainController中生成唯一请求ID,调用外部API时将ID传入请求头和Callback参数,然后等待回调任务完成:
@RestController @RequestMapping("/main") public class MainController { @Autowired private RestTemplate restTemplate; @Autowired private CallbackTaskHolder taskHolder; @PostMapping("/call-api") public ResponseEntity<Map<String, Object>> callApi() throws InterruptedException, ExecutionException, TimeoutException { // 生成唯一请求关联ID String requestId = UUID.randomUUID().toString(); // 构建外部API请求头,包含Authorization、Content-Type和带requestId的Callback地址 HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.set("Authorization", "your-auth-token"); headers.set("Callback", "http://mycallback:8080/sendCallback?requestId=" + requestId); // 构造请求体 SamplePlayload requestPayload = createRequestPayload(); HttpEntity<SamplePlayload> requestEntity = new HttpEntity<>(requestPayload, headers); // 调用外部API获取即时响应 ResponseEntity<String> initialResponseEntity = restTemplate.exchange( "http://sample:7070/test", HttpMethod.POST, requestEntity, String.class ); String initialResponse = initialResponseEntity.getBody(); // 创建异步Future并注册到任务容器 CompletableFuture<CallbackResponse> callbackFuture = new CompletableFuture<>(); taskHolder.registerTask(requestId, callbackFuture); // 等待回调结果(设置30秒超时,避免无限阻塞) CallbackResponse callbackResponse = callbackFuture.get(30, TimeUnit.SECONDS); // 封装两个响应返回 Map<String, Object> result = new HashMap<>(); result.put("initialResponse", initialResponse); result.put("callbackResponse", callbackResponse); return ResponseEntity.ok(result); } private SamplePlayload createRequestPayload() { // 构造请求体逻辑 return new SamplePlayload(); } }
3. 修改回调控制器
CallbackController收到回调后,通过requestId找到对应的异步任务并完成:
@RestController @RequestMapping("/sendCallback") public class CallbackController { @Autowired private CallbackTaskHolder taskHolder; @PostMapping public ResponseEntity<Void> handleCallback(@RequestBody CallbackResponse callbackResponse, @RequestParam String requestId) { // 完成对应的异步任务,传递回调响应 taskHolder.completeTask(requestId, callbackResponse); return ResponseEntity.ok().build(); } }
注意事项
- 超时处理:必须为
CompletableFuture.get()设置超时时间,防止请求长期阻塞导致资源耗尽。 - 内存泄漏:通过定时任务清理已完成或超时的任务,避免
ConcurrentHashMap无限膨胀。 - 线程安全:使用
ConcurrentHashMap保证多线程环境下的任务存储安全。 - 异常处理:需捕获
TimeoutException、ExecutionException等异常,避免请求直接报错。
内容的提问来源于stack exchange,提问作者Md. Sakhawath Hossain
相关产品推荐
相关产品推荐

