问题回溯

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

商家中心报错(JSON串):

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

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

其他业务代码:

/**
* 模板下载
*/
@RequestMapping("/doBatchWareSetAd")
public void doBatchWareSetAd(@RequestParam MultipartFile file, HttpServletResponse response) {
wareBatchBusiness.doBatchWareSetAd(file, response, getLongOrgCode(), getCurrentUserPin(), getCurrentUserId());
}

问题业务代码:

/**
* 模板下载
*/
@RequestMapping("/doBatchWareSetAdDemo")
@ResponseBody
public Map<String, Object> doBatchWareSetAd(@RequestParam MultipartFile file, HttpServletResponse response) {
return wareBatchBusiness.doBatchWareSetAd(file, response, getLongOrgCode(), getCurrentUserPin(), getCurrentUserId());
}

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

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

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

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

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

代码案例1:

@RequestMapping("/test1")
@ResponseBody
public Map<String, String> test1(HttpServletResponse response) {
Map<String, String> map = new HashMap<>();
map.put("1", "1");
return map;
} // 响应
JSON报文

跟代码发现其核心处理类为: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<String, String> test2(HttpServletResponse response) throws IOException {
Map<String, String> 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;
} // 响应
提示下载文件

关键DEBUG源码截图:

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

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

结论与启发

结论:

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

启发:

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

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

@RequestMapping("/test1")
@ResponseBody
public Map<String, String> test1(HttpServletResponse response) {
Map<String, String> 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;
}

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

作者:京东零售 柯贤铭

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

主动写入流对@ResponseBody注解的影响的更多相关文章

  1. SpringMVC学习八 @ResponseBody注解

    (一)在方法上只有@RequestMapping 时,无论方法返回值是什么认为需要跳转,代码实例如下 @RequestMapping("demo10") public People ...

  2. SpringMVC源码剖析5:消息转换器HttpMessageConverter与@ResponseBody注解

    转自 SpringMVC关于json.xml自动转换的原理研究[附带源码分析] 本系列文章首发于我的个人博客:https://h2pl.github.io/ 欢迎阅览我的CSDN专栏:Spring源码 ...

  3. @RequestBody、@ResponseBody注解是如何将输入输出转换成json的

    @RequestBody.@ResponseBody注解,可以直接将输入解析成Json.将输出解析成Json,但HTTP 请求和响应是基于文本的,意味着浏览器和服务器通过交换原始文本进行通信,而这里其 ...

  4. @RequestBody, @ResponseBody 注解详解

    简介: @RequestBody 作用: i) 该注解用于读取Request请求的body部分数据,使用系统默认配置的HttpMessageConverter进行解析,然后把相应的数据绑定到要返回的对 ...

  5. @responseBody注解的使用

    1. @responseBody注解的作用是将controller的方法返回的对象通过适当的转换器转换为指定的格式之后,写入到response对象的body区,通常用来返回JSON数据或者是XML 数 ...

  6. 在什么情况下使用@ResponseBody 注解?

    @Controller @RequestMapping("/") public class HelloController { @RequestMapping(value = &q ...

  7. @ResponseBody注解和@RequestBody注解

    @ResponseBody:表示该方法的返回结果直接写入HTTP response body中一般在异步获取数据时使用, 在使用@RequestMapping后,返回值通常解析为跳转路径,加上@Res ...

  8. @ResponseBody注解

    作用 @ResponseBody注解表示该方法的返回结果直接写入HTTP response body中 原理 在使用此注解之后跳过视图处理器,将返回的对象通过适当的转换器转换为指定的格式之后,直接将数 ...

  9. @RequestBody, @ResponseBody 注解详解(转)

    原文地址: https://www.cnblogs.com/qq78292959/p/3760651.html @RequestBody, @ResponseBody 注解详解(转) 引言: 接上一篇 ...

  10. SpringMVC拦截器和@ResponseBody注解冲突

    在使用@ResponseBody注解后controller方法只会返回ModelandView对象的数据模型,不会返回视图,这样有很多好处,但是如果在拦截器中进行了页面转发,在满足页面转发条件时,不会 ...

随机推荐

  1. R 语言中 X11 相关的一些问题

    参考 Anaconda 官方文档<Using R language with Anaconda>安装 R-4.0.2: conda create -n r-4.0.2 r-essentia ...

  2. 【python基础】if语句-语法格式

    if语句-语法格式 简单理解if语句之后,我们的if语句语法格式有多种,选择使用哪种取决于要测试的条件数 1.if结构 最简单的if语句只有一个条件测试和一个代码块 其语法格式: 假设想要指导一个学员 ...

  3. Jconsole 开启远程连接遇到的一些坑

    最近在学习 JVM,其中涉及到性能.内存等指标分析需要使用工具分享,Java 提供了几个可视化工具来监控和管理 Java 应用,比如 Jconsole.JVisual.JMC,他们以图形化的界面实时的 ...

  4. python的jsonpath 提取器

    jsonpath 解析 接下来讲一个非常强大并且方便的 jsonpath 专门用于 json 解析,解决路径深的老大难问题!先安装依赖包 pip install jsonpath 学习jsonpath ...

  5. 【Spring boot】 @Value注解

    一.不通过配置文件的注入属性 1.1 注入普通字符串 直接附在属性名上,在 Bean 初始化时,会赋初始值 @Value("normal") private String norm ...

  6. modulemap的使用方法

    modulemap的作用   modulemap 文件是用来解决 C,Object-C,C++ 代码在 Swift 项目中集成的问题的. 在 Swift 项目中,如果需要使用 C,Object-C 或 ...

  7. FireflySoft.LeaderElection增加基于ZooKeeper的Leader选举

    FireflySoft.LeaderElection的第一个版本实现了基于Consul的Leader选举,考虑到ZooKeeper的一个常见用途也是选主,所以此类库把ZooKeeper也集成了进来.并 ...

  8. DevOps实战:使用GitLab+Jenkins+Kubernetes(k8s)建立CI/CD解决方案

    目录 一.系统环境 二.前言 三.DevOps简介 四.CI/CD简介 五.安装并配置docker参数 六.使用registry搭建镜像仓库 七.安装部署gitlab代码仓库 7.1 创建gitlab ...

  9. ChatGPT 1.0.0安卓分析,仅限国内分享

    ChatGPT 1.0.0安卓分析,仅限国内分享 博客园首发,本文将对ChatGpt Android版本1.0.0 APK进行静态解包分析和抓包分析,从ChatGpt Android APK功能的设计 ...

  10. 2021-7-6 vue音乐播放器

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script sr ...