作者:京東零售 柯賢銘
問(wèn)題回溯
2023年Q2某日運(yùn)營(yíng)反饋一個(gè)問(wèn)題,商品系統(tǒng)商家中心某批量工具模板無(wú)法下載,導(dǎo)致功能無(wú)法使用(因?yàn)槟0迨莿?dòng)態(tài)變化的)
商家中心報(bào)錯(cuò)(JSON串):
{"code":-1,"msg":"失敗"}
?
負(fù)責(zé)的同事看到失敗后立即與我展開(kāi)討論(因?yàn)椴皇顷P(guān)鍵業(yè)務(wù),所以不需要回滾,修復(fù)即可),我們發(fā)現(xiàn)新功能模板下載的代碼與之前的代碼有所不同,恰好之前的功能又可以正常運(yùn)行,所以同事對(duì)現(xiàn)有代碼進(jìn)行改造然后預(yù)發(fā)布測(cè)試完成后再次上線。
?
其他業(yè)務(wù)代碼:
/** * 模板下載 */ @RequestMapping("/doBatchWareSetAd") public void doBatchWareSetAd(@RequestParam MultipartFile file, HttpServletResponse response) { wareBatchBusiness.doBatchWareSetAd(file, response, getLongOrgCode(), getCurrentUserPin(), getCurrentUserId()); }
?
問(wèn)題業(yè)務(wù)代碼:
/** * 模板下載 */ @RequestMapping("/doBatchWareSetAdDemo") @ResponseBody public Map doBatchWareSetAd(@RequestParam MultipartFile file, HttpServletResponse response) { return wareBatchBusiness.doBatchWareSetAd(file, response, getLongOrgCode(), getCurrentUserPin(), getCurrentUserId()); }
?
上線的結(jié)果是;仍然無(wú)法使用。
其實(shí)也正常:因?yàn)閮煞N代碼在預(yù)發(fā)布都可以正常運(yùn)行,在線上出錯(cuò)只可能是因?yàn)槠渌颍徊贿^(guò)我們不了解底層原理,害怕它 "可能" 有問(wèn)題罷了,最終查詢得到的結(jié)論是權(quán)限系統(tǒng)管理員在線上環(huán)境沒(méi)有給我們配置相應(yīng)的文件,導(dǎo)致請(qǐng)求為空,導(dǎo)致請(qǐng)求失敗。
?
探索 @ResponseBody 與主動(dòng)寫(xiě)入流的關(guān)系
我們都知道 @ResponseBody 注解可以幫助我們把返回對(duì)象轉(zhuǎn)化為JSON,方便展示和交互。
那它到底是如何工作的呢,請(qǐng)看下面的講解:
?
代碼案例1:
@RequestMapping("/test1") @ResponseBody public Map test1(HttpServletResponse response) { Map map = new HashMap?>(); map.put("1", "1"); return map; } // 響應(yīng) JSON報(bào)文
?
跟代碼發(fā)現(xiàn)其核心處理類為:RequestResponseBodyMethodProcessor.java
方法:org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#handleReturnValue 會(huì)處理其相關(guān)返回值。
真正的核心處理方法:org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#writeWithMessageConverters
關(guān)鍵DEBUG記錄如圖所示:
?
后續(xù)內(nèi)容可以想象,肯定還有地方去把流按照指定的HEADER寫(xiě)入,因?yàn)楹捅疚臒o(wú)關(guān)所以不深究。
?
再來(lái)看代碼案例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; } // 響應(yīng) 提示下載文件
?
關(guān)鍵DEBUG源碼截圖:
?
可以發(fā)現(xiàn)Spring對(duì)這種方式操作文件流視作異常情況,然后拋出,在后續(xù)邏輯中完成整個(gè)請(qǐng)求,簡(jiǎn)單來(lái)說(shuō)就是 @ResponseBody 注解沒(méi)起到任何作用。
因此答案呼之欲出:當(dāng)時(shí)功能不可用的罪魁禍?zhǔn)拙褪窍嚓P(guān)人員沒(méi)有配置參數(shù)導(dǎo)致,與寫(xiě)法沒(méi)有任何關(guān)系。
?
結(jié)論與啟發(fā)
結(jié)論:
1.我們要相信自己的代碼,至少是要相信已經(jīng)經(jīng)過(guò)測(cè)試的代碼。
2.在委托他人或者自己配置環(huán)境參數(shù),如權(quán)限、ZK等每次都保證預(yù)發(fā)布和線上同時(shí)配置,避免遺漏的情況。
?
啟發(fā):
聊了這么多,那我們這種類似場(chǎng)景的代碼應(yīng)該怎么寫(xiě)?
既然主動(dòng)寫(xiě)入流會(huì)解除@ResponseBody的作用,反之又能發(fā)揮它的作用,那我們最佳方案是不是如下所示?
@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; }
?
如此一來(lái),當(dāng)發(fā)生預(yù)期之外的情況,我們有非常明顯的報(bào)錯(cuò)提示,當(dāng)正常時(shí)又可以完美實(shí)現(xiàn)功能,妙哉(我覺(jué)得)~
審核編輯 黃宇
-
代碼
+關(guān)注
關(guān)注
30文章
4886瀏覽量
70216 -
JSON
+關(guān)注
關(guān)注
0文章
121瀏覽量
7256
發(fā)布評(píng)論請(qǐng)先 登錄
如何通過(guò)注解來(lái)優(yōu)化我們的Java代碼
HarmonyOS注解的使用方法分享
分析java注解基本概念
注解定義Bean及開(kāi)發(fā)
Spring Web MVC注解

Spring Dependency Inject與Bean Scops注解

容器配置及Spring Boot注解

Springboot常用注解合集

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

評(píng)論