ToB企服应用市场:ToB评测及商务社交产业平台

标题: 主动写入流对@ResponseBody注解的影响 [打印本页]

作者: 鼠扑    时间: 2023-9-23 21:15
标题: 主动写入流对@ResponseBody注解的影响
问题回溯

2023年Q2某日运营反馈一个问题,商品系统商家中心某批量工具模板无法下载,导致功能无法使用(因为模板是动态变化的)
商家中心报错(JSON串):
  1. {"code":-1,"msg":"失败"}
复制代码
负责的同事看到失败后立即与我展开讨论(因为不是关键业务,所以不需要回滚,修复即可),我们发现新功能模板下载的代码与之前的代码有所不同,恰好之前的功能又可以正常运行,所以同事对现有代码进行改造然后预发布测试完成后再次上线。
其他业务代码:
  1. /**
  2. * 模板下载
  3. */
  4. @RequestMapping("/doBatchWareSetAd")
  5. public void doBatchWareSetAd(@RequestParam MultipartFile file, HttpServletResponse response) {
  6.         wareBatchBusiness.doBatchWareSetAd(file, response, getLongOrgCode(), getCurrentUserPin(), getCurrentUserId());
  7. }
复制代码
问题业务代码:
  1. /**
  2. * 模板下载
  3. */
  4. @RequestMapping("/doBatchWareSetAdDemo")
  5. @ResponseBody
  6. public Map<String, Object> doBatchWareSetAd(@RequestParam MultipartFile file, HttpServletResponse response) {
  7.         return wareBatchBusiness.doBatchWareSetAd(file, response, getLongOrgCode(), getCurrentUserPin(), getCurrentUserId());
  8. }
复制代码
上线的结果是;仍然无法使用。
其实也正常:因为两种代码在预发布都可以正常运行,在线上出错只可能是因为其他原因,只不过我们不了解底层原理,害怕它 "可能" 有问题罢了,最终查询得到的结论是权限系统管理员在线上环境没有给我们配置相应的文件,导致请求为空,导致请求失败。
探索 @ResponseBody 与主动写入流的关系

我们都知道 @ResponseBody 注解可以帮助我们把返回对象转化为JSON,方便展示和交互。
那它到底是如何工作的呢,请看下面的讲解:
代码案例1:
  1. @RequestMapping("/test1")
  2. @ResponseBody
  3. public Map<String, String> test1(HttpServletResponse response) {
  4.     Map<String, String> map = new HashMap<>();
  5.     map.put("1", "1");
  6.     return map;
  7. }
  8. // 响应
  9. JSON报文
复制代码
跟代码发现其核心处理类为:RequestResponseBodyMethodProcessor.java
方法:org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#handleReturnValue 会处理其相关返回值。
真正的核心处理方法:org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#writeWithMessageConverters
关键DEBUG记录如图所示:

后续内容可以想象,肯定还有地方去把流按照指定的HEADER写入,因为和本文无关所以不深究。
再来看代码案例2:
  1. @RequestMapping("/test2")
  2. @ResponseBody
  3. public Map<String, String> test2(HttpServletResponse response) throws IOException {
  4.     Map<String, String> map = new HashMap<>();
  5.     map.put("1", "1");
  6.     response.setContentType("application/vnd.ms-excel");
  7.     response.setHeader("Content-Disposition", String.format(
  8.         "attachment; filename=%s_%s.xls", "Demo", System.currentTimeMillis()));
  9.     OutputStream out = response.getOutputStream();
  10.     out.flush();
  11.     out.close();
  12.     return map;
  13. }
  14. // 响应
  15. 提示下载文件
复制代码
关键DEBUG源码截图:


可以发现Spring对这种方式操作文件流视作异常情况,然后抛出,在后续逻辑中完成整个请求,简单来说就是 @ResponseBody 注解没起到任何作用。
因此答案呼之欲出:当时功能不可用的罪魁祸首就是相关人员没有配置参数导致,与写法没有任何关系。
结论与启发

结论:
启发:
聊了这么多,那我们这种类似场景的代码应该怎么写?
既然主动写入流会解除@ResponseBody的作用,反之又能发挥它的作用,那我们最佳方案是不是如下所示?
  1. @RequestMapping("/test1")
  2. @ResponseBody
  3. public Map<String, String> test1(HttpServletResponse response) {
  4.     Map<String, String> map = new HashMap();
  5.     if (获取不到文件配置 == true) {
  6.         return map.put("msg", "获取不到文件配置");
  7.     }
  8.    
  9.     response.setContentType("application/vnd.ms-excel");
  10.     response.setHeader("Content-Disposition", String.format(
  11.         "attachment; filename=%s_%s.xls", "Demo", System.currentTimeMillis()));
  12.     OutputStream out = response.getOutputStream();
  13.     out.flush();
  14.     out.close();
  15.     return map;
  16. }
复制代码
如此一来,当发生预期之外的情况,我们有非常明显的报错提示,当正常时又可以完美实现功能,妙哉(我觉得)~
作者:京东零售 柯贤铭
来源:京东云开发者社区 转载请注明来源

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4