• 主动写入流对@ResponseBody注解的影响 | 京东云技术团队


    问题回溯

    2023年Q2某日运营反馈一个问题,商品系统商家中心某批量工具模板无法下载,导致功能无法使用(因为模板是动态变化的)

    商家中心报错(JSON串):

    {"code":-1,"msg":"失败"}
    
    • 1

    负责的同事看到失败后立即与我展开讨论(因为不是关键业务,所以不需要回滚,修复即可),我们发现新功能模板下载的代码与之前的代码有所不同,恰好之前的功能又可以正常运行,所以同事对现有代码进行改造然后预发布测试完成后再次上线。

    其他业务代码:

    /**
     * 模板下载
     */
    @RequestMapping("/doBatchWareSetAd")
    public void doBatchWareSetAd(@RequestParam MultipartFile file, HttpServletResponse response) {
    	wareBatchBusiness.doBatchWareSetAd(file, response, getLongOrgCode(), getCurrentUserPin(), getCurrentUserId());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    问题业务代码:

    /**
     * 模板下载
     */
    @RequestMapping("/doBatchWareSetAdDemo")
    @ResponseBody
    public Map doBatchWareSetAd(@RequestParam MultipartFile file, HttpServletResponse response) {
    	return wareBatchBusiness.doBatchWareSetAd(file, response, getLongOrgCode(), getCurrentUserPin(), getCurrentUserId());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    上线的结果是;仍然无法使用。

    其实也正常:因为两种代码在预发布都可以正常运行,在线上出错只可能是因为其他原因,只不过我们不了解底层原理,害怕它 “可能” 有问题罢了,最终查询得到的结论是权限系统管理员在线上环境没有给我们配置相应的文件,导致请求为空,导致请求失败。

    探索 @ResponseBody 与主动写入流的关系

    我们都知道 @ResponseBody 注解可以帮助我们把返回对象转化为JSON,方便展示和交互。

    那它到底是如何工作的呢,请看下面的讲解:

    代码案例1:

    @RequestMapping("/test1")
    @ResponseBody
    public Map test1(HttpServletResponse response) {
        Map map = new HashMap<>();
        map.put("1", "1");
        return map;
    }
    
    // 响应
    JSON报文
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    跟代码发现其核心处理类为:RequestResponseBodyMethodProcessor.java

    方法:org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#handleReturnValue 会处理其相关返回值。

    真正的核心处理方法:org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#writeWithMessageConverters

    关键DEBUG记录如图所示:

    后续内容可以想象,肯定还有地方去把流按照指定的HEADER写入,因为和本文无关所以不深究。

    再来看代码案例2:

    @RequestMapping("/test2")
    @ResponseBody
    public Map test2(HttpServletResponse response) throws IOException {
        Map map = new HashMap<>();
        map.put("1", "1");
    
        response.setContentType("application/vnd.ms-excel");
        response.setHeader("Content-Disposition", String.format(
            "attachment; filename=%s_%s.xls", "Demo", System.currentTimeMillis()));
    
        OutputStream out = response.getOutputStream();
        out.flush();
        out.close();
        return map;
    }
    
    // 响应
    提示下载文件
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    关键DEBUG源码截图

    可以发现Spring对这种方式操作文件流视作异常情况,然后抛出,在后续逻辑中完成整个请求,简单来说就是 @ResponseBody 注解没起到任何作用。

    因此答案呼之欲出:当时功能不可用的罪魁祸首就是相关人员没有配置参数导致,与写法没有任何关系。

    结论与启发

    结论:

    1. 我们要相信自己的代码,至少是要相信已经经过测试的代码。
    2. 在委托他人或者自己配置环境参数,如权限、ZK等每次都保证预发布和线上同时配置,避免遗漏的情况。

    启发:

    聊了这么多,那我们这种类似场景的代码应该怎么写?

    既然主动写入流会解除@ResponseBody的作用,反之又能发挥它的作用,那我们最佳方案是不是如下所示?

    @RequestMapping("/test1")
    @ResponseBody
    public Map test1(HttpServletResponse response) {
        Map map = new HashMap();
        if (获取不到文件配置 == true) {
            return map.put("msg", "获取不到文件配置");
        }
        
        response.setContentType("application/vnd.ms-excel");
        response.setHeader("Content-Disposition", String.format(
            "attachment; filename=%s_%s.xls", "Demo", System.currentTimeMillis()));
    
        OutputStream out = response.getOutputStream();
        out.flush();
        out.close();
        return map;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    如此一来,当发生预期之外的情况,我们有非常明显的报错提示,当正常时又可以完美实现功能,妙哉(我觉得)~

    作者:京东零售 柯贤铭

    来源:京东云开发者社区 转载请注明来源

  • 相关阅读:
    前端基础建设与架构30 实现高可用:使用 Puppeteer 生成性能最优的海报系统
    Flutter与原生通信(上)
    英语考试的作文模板
    工程项目管理系统的Java实现:高效协同与信息共享
    高等数学(第七版)同济大学 习题7-5 个人解答
    java计算机毕业设计足球信息发布平台源码+数据库+系统+部署+lw文档
    [附源码]Java计算机毕业设计SSM高铁乘坐舒适性在线调查及评价系统
    WIN11系统设置重启与睡眠唤醒后自动拨号
    FPGA中时序和组合逻辑语法对应的时钟波形
    【第四阶段】kotlin语言的构造函数学习
  • 原文地址:https://blog.csdn.net/JDDTechTalk/article/details/133021185