0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

主动写入流对@ResponseBody注解的影响

京东云 来源:京东零售 柯贤铭 作者:京东零售 柯贤铭 2024-10-29 11:28 次阅读

作者:京东零售 柯贤铭

问题回溯

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记录如图所示:

wKgaoWcgVl6ASzeIAACPuUuQfBA158.png



后续内容可以想象,肯定还有地方去把流按照指定的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源码截图:

wKgZoWcgVl-AEfpSAAA2pk7ToUA050.png

wKgaoWcgVmCADqguAAAlKaZXGoY120.png



可以发现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;
}



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

审核编辑 黄宇

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 代码
    +关注

    关注

    30

    文章

    4841

    浏览量

    69195
  • JSON
    +关注

    关注

    0

    文章

    119

    浏览量

    7035
收藏 人收藏

    评论

    相关推荐

    如何通过注解来优化我们的Java代码

    Java注解可以说是我们编码过程中最常用的。本篇文章将给大家介绍Java注解的概念、作用以及如何使用注解来提升代码的可读性和灵活性,并介绍如何通过注解来优化我们的Java代码。 1、什
    的头像 发表于 09-30 11:39 710次阅读

    机种新品导入流

    机种新品导入流
    发表于 08-11 10:08

    redis缓存注解怎么使用

    spring boot —— redis 缓存注解使用教程
    发表于 09-11 14:43

    HarmonyOS注解的使用方法分享

    javax.annotation.processing.Processor 文件写入注解处理器的全称,包括包路径4、使用注解在entry模块的build.gradle文件中添加依赖dependencies
    发表于 03-28 14:04

    分析java注解基本概念

    什么是注解(Annotation): Annotation(注解)就是Java提供了一种元程序中的元素关联任何信息和着任何元数据(metadata)的途径和方法。Annotion(注解)是一个接口
    发表于 09-27 14:53 0次下载

    注解定义Bean及开发

    注解本质是一个继承了Annotation 的特殊接口,其具体实现类是Java 运行时生成的动态代理类。
    发表于 08-02 10:26 463次阅读

    Spring Web MVC注解

    RequestMapping注解的主要用途是将Web请求与请求处理类中的方法进行映射。Spring MVC和Spring WebFlux都通过`RquestMappingHandlerMapping`和`RequestMappingHndlerAdapter`两个类来提供对@RequestMapping
    的头像 发表于 04-07 11:32 669次阅读
    Spring Web MVC<b class='flag-5'>注解</b>

    Spring Dependency Inject与Bean Scops注解

    DependsOn`注解可以配置Spring IoC容器在初始化一个Bean之前,先初始化其他的Bean对象。下面是此注解使用示例代码:
    的头像 发表于 04-07 11:35 744次阅读
    Spring Dependency Inject与Bean Scops<b class='flag-5'>注解</b>

    容器配置及Spring Boot注解

    Autowired注解用于标记Spring将要解析和注入的依赖项。此注解可以作用在构造函数、字段和setter方法上。
    的头像 发表于 04-07 11:45 629次阅读
    容器配置及Spring Boot<b class='flag-5'>注解</b>

    Springboot常用注解合集

    前几章,在系统启动类里面,都加入了此启动注解,此注解是个组合注解,包括了`@SpringBootConfiguration`、`@EnableAutoConfiguration`和`@ComponentScan`
    的头像 发表于 04-07 14:27 804次阅读
    Springboot常用<b class='flag-5'>注解</b>合集

    JAVA中注解是怎么做到的(上)

    注解想必大家在项目中经常使用,比如Spring框架中常用的一些注解:`@Controller`、`@Service`、`@RequestMapping`等等,它是JDK1.5及以后版本引入的一个特性
    的头像 发表于 05-11 10:57 689次阅读

    JAVA中注解是怎么做到的(下)

    注解想必大家在项目中经常使用,比如Spring框架中常用的一些注解:`@Controller`、`@Service`、`@RequestMapping`等等,它是JDK1.5及以后版本引入的一个特性
    的头像 发表于 05-11 10:57 623次阅读
    JAVA中<b class='flag-5'>注解</b>是怎么做到的(下)

    springmvc常用5种注解

    SpringMVC是一种基于Java的Web框架,使用注解可以更加方便灵活地开发和管理控制器,实现请求的映射和处理。在SpringMVC中,有许多常用的注解,本文将详细介绍其中的五种注解,并且详细
    的头像 发表于 11-22 16:51 992次阅读

    springboot核心注解

    Spring Boot 是基于 Spring 框架的开源框架,它可以帮助开发者快速构建、部署和运行独立的、生产级的 Spring 应用程序。Spring Boot 提供了一系列核心注解,这些注解可以
    的头像 发表于 11-23 09:23 572次阅读

    SpringBoot核心注解由几个注解组成

    简化应用程序开发的注解,其中核心注解包括 @SpringBootApplication、@RestController、@RequestMapping、@Autowired、@ComponentScan
    的头像 发表于 12-03 15:09 826次阅读