如何将Java Controller中Executor Service的执行结果输出到前端视图
问题原因
你当前的实现逻辑里,submit接口收到请求后,把doStuff扔给线程池异步执行就直接返回了ok,此时对应的HTTP请求生命周期已经完全结束,后续doStuff的执行结果没有办法通过已经关闭的HTTP连接返回给前端,所以你直接在doStuff里返回值也无法传递到前端。
可选解决方案
方案1:前端短轮询(实现成本最低,适合大多数场景)
不需要改造现有架构,只需要加少量代码即可实现:
- 后端逻辑调整:
- 生成唯一的任务ID,异步任务执行完成后,把结果
x和任务ID绑定存入缓存(单节点部署用本地Map,分布式部署用Redis) submit接口不再只返回ok,同时返回生成的任务ID给前端- 新增查询接口,接收任务ID,返回对应任务的执行状态和结果
- 生成唯一的任务ID,异步任务执行完成后,把结果
- 前端逻辑调整:
- 拿到任务ID后,每隔固定时间调用一次查询接口拉取任务状态
- 查到任务执行完成后,把结果渲染到页面,停止轮询
改造后的代码示例
后端代码
// 本地任务结果缓存,分布式场景替换为Redis即可 private ConcurrentHashMap<String, String> taskResultCache = new ConcurrentHashMap<>(); @RequestMapping(value = "/submit", method = RequestMethod.POST, produces = "application/json") public @ResponseBody String submitSurvey1(@ModelAttribute("uploadForm") UploadTest uploadForm, Model map, HttpSession session, HttpServletRequest request) { // 生成唯一任务ID String taskId = UUID.randomUUID().toString(); // 提交异步任务 executor.submit(() -> { String result = doStuff(uploadForm, map, session, request); // 执行完成后缓存结果 taskResultCache.put(taskId, result); }); // 返回任务ID给前端 return "{\"code\":\"ok\",\"taskId\":\""+taskId+"\"}"; } // 新增任务结果查询接口 @RequestMapping(value = "/getTaskResult", method = RequestMethod.GET, produces = "application/json") public @ResponseBody String getTaskResult(@RequestParam String taskId) { String result = taskResultCache.get(taskId); if(result == null){ return "{\"status\":\"processing\"}"; }else { // 取完结果删除缓存,避免内存泄漏 taskResultCache.remove(taskId); return "{\"status\":\"finished\",\"result\":"+result+"}"; } } // doStuff修改返回值为String,返回执行结果x private String doStuff(@ModelAttribute("uploadForm") UploadTest uploadForm, Model map, HttpSession session, HttpServletRequest request){ String x = ""; try { token.regenToken(session); JSONObject hit = new JSONObject(); hit.put("pathfile", "path"); hit.put("filename", "filename"); hit.put("submitted_by", session.getAttribute("session_id").toString()); JSONObject json = new JSONObject(new RestServiceUnirest().requestPost( this.API + "/SubmissionLimit/uploadrecord/ec", session.getAttribute("accesskey").toString(), hit.toString())); x = json.toString(); } catch (Exception e) { file.delete(); x = "{\"error\":\""+e.getMessage()+"\"}"; } return x; }
前端代码
$("#formInput").submit(function (e) { $("#submitFile").html("<i class='fa fa-circle-o-notch fa-spin'></i> Load..").prop("disabled", true); $.ajax({ type: "POST", url: "${local_server}/UploadController/submit", data: new FormData(this), processData: false, contentType: false, cache: false, dataType: "json", success: function (res) { if(res.code == "ok"){ const taskId = res.taskId; // 每2秒轮询一次结果 const pollTimer = setInterval(()=>{ $.get("${local_server}/UploadController/getTaskResult", {taskId: taskId}, function(data){ if(data.status == "finished"){ // 这里替换为你自己的页面渲染逻辑 console.log("执行结果:", data.result); // 恢复按钮状态 $("#submitFile").html("提交").prop("disabled", false); // 停止轮询 clearInterval(pollTimer); } }, "json"); }, 2000); // 额外加超时逻辑,避免任务异常一直轮询 setTimeout(()=>{ clearInterval(pollTimer); $("#submitFile").html("提交").prop("disabled", false); alert("任务执行超时,请稍后重试"); }, 5*60*1000) } } }); e.preventDefault(); });
方案2:WebSocket主动推送(实时性更高)
如果业务对结果通知的延迟要求很高,可以选择WebSocket方案:
- 前端页面加载时,和后端建立WebSocket长连接,绑定当前用户的sessionId
- 后端
doStuff执行完成后,直接通过对应session的WebSocket连接,把结果主动推送给前端 - 前端收到推送消息后直接渲染到页面即可,不需要轮询
内容的提问来源于stack exchange,提问作者Loka
相关产品推荐
相关产品推荐

