问题回溯

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. 搭建一个属于自己的springboot项目

    一.确定环境 最近公司要上个新系统,指定由我来带两个人进行开发,既然是新项目,那么项目搭建的事就落到我的头上了.现在都是使用springboot进行开发,为此我搭环境使用的是springboot,具体 ...

  2. 【Python&GIS】GDAL栅格转面&计算矢量面积

            GDAL(Geospatial Data Abstraction Library)是一个在X/MIT许可协议下的开源栅格空间数据转换库.它利用抽象数据模型来表达所支持的各种文件格式.它 ...

  3. Rust 通用编程概念

    通用编程概念 变量.基本类型.函数.控制流 变量与可变性 rust中的变量默认是不可变的,这样是为了能够让你安全并且方便地写出复杂.甚至并行的代码. 当一个变量是不可变时,一旦它绑定到了某个值上面,这 ...

  4. 探秘高逼格艺术二维码的制作过程-AI绘画图生图

    在之前的文章<AI制作艺术二维码-文生图>中,我介绍了一种直接通过提示词生成高逼格二维码的方法,但是通过提示词我们无法很好的控制生成图片的样式,特别是有些同学想要将自己的Logo或者头像附 ...

  5. 我不知道的threejs(6)-开发中的容易被忽略的

    在threejs Editor中调好一些样式属性后, 可以直接选择导出具体的格式,或者导出成json[json 一般体积大很多,比glb](场景,通过objectLoader 加载json!!!) 自 ...

  6. Win32API中的宽字符

    4.1了解什么是Win32API Win32API就是windows操作系统提供给我们的函数(应用程序接口),其主要存放在C:\Windows\System32 (存储的DLL是64位).C:\Win ...

  7. MAUI Blazor Android 输入框软键盘遮挡问题

    前言 最近才发现MAUI Blazor Android存在输入框软键盘遮挡这个问题,搜索了一番,原来这是安卓webview一个由来已久的问题,还好有大佬提出了解决方案 AndroidBug5497Wo ...

  8. 一分钟学一个 Linux 命令 - rm

    前言 大家好,我是 god23bin,欢迎回到咱们的<一分钟学一个 Linux 命令>系列,今天我要讲的是一个比较危险的命令,rm 命令,没错,你可以没听过 rm 命令,但是删库跑路你不可 ...

  9. 第十六届全国大学生 信息安全竞赛创新实践能力赛wp

    这是我第一次参加ctf,有许多东西都还不会,感觉有一些题挺有趣的,多积累积累经验吧. crypto Sign_in_passwd 下发了一个叫flag的文件,用记事本打开发现是两行加密,第一行看着像b ...

  10. SpringCloud-Hystrix服务熔断与降级工作原理&源码

    先附上Hystrix源码图 在微服务架构中,根据业务来拆分成一个个的服务,服务与服务之间可以相互调用(RPC),在Spring Cloud可以用RestTemplate+Ribbon和Feign来调用 ...