SpringBoot如何配置獲取request中body的json格式參數(shù)
背景
最近開(kāi)發(fā)項(xiàng)目,因?yàn)橛械谌秸{(diào)用我們的接口,我們使用SpringBoot以JavaBean的方式接收了我們預(yù)期的參數(shù),參數(shù)接收也沒(méi)有什么異常。但是有一些需求問(wèn)題需要溝通,需要拿到合作第三方傳入的所有參數(shù),來(lái)進(jìn)行參數(shù)核驗(yàn)。
如何拿到請(qǐng)求的所有參數(shù)呢?正常的思路肯定是從request中獲取,如果是GET請(qǐng)求,參數(shù)在請(qǐng)求路徑中拼接;如果是POST請(qǐng)求,參數(shù)在request的請(qǐng)求體(body)中。
一番檢索,很容易拿到相關(guān)代碼。但是經(jīng)過(guò)實(shí)操,發(fā)現(xiàn)并不能如期獲取到參數(shù)。經(jīng)過(guò)思考,我的接口是POST請(qǐng)求,參數(shù)形式是json格式(使用了@RequestBody來(lái)修飾參數(shù))。
具體過(guò)程參看如下分析
獲取請(qǐng)求中的參數(shù)(非json格式參數(shù))
獲取方法
方法一
Enumeration<String> parameterNames = request.getParameterNames(); while (parameterNames.hasMoreElements()) { String paraName = parameterNames.nextElement(); System.out.println(paraName + ": " + request.getParameter(paraName)); }
方法二
Map<String, String[]> map = request.getParameterMap(); Set<Map.Entry<String, String[]>> keSet = map.entrySet(); for (Iterator<Map.Entry<String, String[]>> itr = keSet.iterator(); itr.hasNext(); ) { Map.Entry<String, String[]> me = itr.next(); Object ok = me.getKey(); Object ov = me.getValue(); String[] value = new String[1]; if (ov != null) { value = (String[]) ov; } else { value[0] = ov.toString(); } for (int k = 0; k < value.length; k++) { System.out.println(ok + "=" + value[k]); } }
結(jié)論
經(jīng)過(guò)測(cè)試以上兩個(gè)方法可以獲取GET請(qǐng)求的參數(shù),以及參數(shù)格式為form-data、x-www-form-urlencoded的POST請(qǐng)求,但是json格式參數(shù)(postman中為raw)的參數(shù)不能獲得。
根據(jù)代碼的簡(jiǎn)介程度,選擇方法一,明顯更舒服一些。
以上結(jié)論經(jīng)過(guò)postman實(shí)測(cè).
獲取POST請(qǐng)求json格式的參數(shù)
以上方法已經(jīng)可以獲取大多數(shù)情況下的請(qǐng)求的參數(shù),但是明顯還不能滿足需求,需要獲取傳入json格式的參數(shù)。
經(jīng)過(guò)檢索推薦方法(參看后邊完整方法)
經(jīng)過(guò)一番檢索,網(wǎng)上推薦的方法一般都是使用流來(lái)進(jìn)行參數(shù)讀取,即使用getInputStream()和getReader():
Map<String, Object> params = new HashMap<>(); BufferedReader br = null; try { try { br = request.getReader(); } catch (IOException e) { e.printStackTrace(); } String str; StringBuilder wholeStr = new StringBuilder(); while ((str = Objects.requireNonNull(br).readLine()) != null) { wholeStr.append(str); } if (StrUtil.isNotEmpty(wholeStr.toString())) { params = JSON.parseObject(wholeStr.toString(), Map.class); } } catch (IOException e) { e.printStackTrace(); } System.out.println(params);
遇到的問(wèn)題及解決思路
問(wèn)題1 流不能多次被調(diào)用
但是又會(huì)遇到如下問(wèn)題:
ERROR m.e.handler.GlobalExceptionHandler - getInputStream() has already been called for this request
java.lang.IllegalStateException: getInputStream() has already been called for this request
at org.apache.catalina.connector.Request.getReader(Request.java:1212)
at org.apache.catalina.connector.RequestFacade.getReader(RequestFacade.java:504)
根據(jù)報(bào)錯(cuò)信息分析簡(jiǎn)單來(lái)說(shuō),就是getInputStream()已經(jīng)被調(diào)用了,不能再次調(diào)用??墒俏铱创a上,我也沒(méi)調(diào)用。經(jīng)過(guò)一番檢索,原來(lái)@RequestBody注解配置后,默認(rèn)會(huì)使用流來(lái)讀取數(shù)據(jù)。
具體原因:
- 默認(rèn)配置時(shí),getInputStream()和getReader()一起使用會(huì)報(bào)錯(cuò),使用兩遍getInputStream(),第二遍會(huì)為空。
- 當(dāng)存在@RequestBody等注解時(shí),springMVC已讀取過(guò)一遍流,默認(rèn)單獨(dú)使用getInputStream()或getReader()都為空。
實(shí)測(cè),不加@RequestBody注解,可以如期獲得請(qǐng)求中的json參數(shù),但是又不得不加@RequestBody注解。這樣就需要新的思路
解決思路
寫(xiě)filter繼承HttpServletRequestWrapper,緩存InputStream,覆蓋getInputStream()和getReader()方法,使用ByteArrayInputStream is = new ByteArrayInputStream(body.getBytes());讀取InputStream。
實(shí)現(xiàn)方法
1.定義增強(qiáng)類,繼承繼承HttpServletRequestWrapper
將請(qǐng)求體中的流copy一份,覆寫(xiě)getInputStream()和getReader()方法供外部使用。每次調(diào)用覆寫(xiě)后的getInputStream()方法都是從復(fù)制出來(lái)的二進(jìn)制數(shù)組中進(jìn)行獲取,這個(gè)二進(jìn)制數(shù)組在對(duì)象存在期間一直存在,這樣就實(shí)現(xiàn)了流的重復(fù)讀取。
public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper { private byte[] body; public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException { super(request); body = HttpHelper.getBodyString(request).getBytes(StandardCharsets.UTF_8); } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(getInputStream())); } @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream bais = new ByteArrayInputStream(body); return new ServletInputStream() { @Override public int read() throws IOException { return bais.read(); } @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener readListener) { } }; } public void setInputStream(byte[] body) { this.body = body; } }
2.構(gòu)建過(guò)濾器
@Slf4j @WebFilter(filterName = "RequestWrapperFilter", urlPatterns = "/api/test/test2") public class RequestWrapperFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException , IOException { ServletRequest requestWrapper = null; if (request instanceof HttpServletRequest) { requestWrapper = new BodyReaderHttpServletRequestWrapper((HttpServletRequest) request); } if (null == requestWrapper) { log.error("過(guò)濾器包裝request失敗!將返回原來(lái)的request"); chain.doFilter(request, response); } else { log.info("過(guò)濾器包裝request成功"); chain.doFilter(requestWrapper, response); } } @Override public void destroy() { } }
注意事項(xiàng):
- 使用@WebFilter注解的urlPatterns屬性,可指定過(guò)濾哪一個(gè)接口方法。
- 過(guò)濾器類最好不要交由Spring管理,否則每一個(gè)接口都會(huì)進(jìn)行過(guò)濾(實(shí)測(cè))。例如:不要添加@Component注解. 3.編寫(xiě)工具類方便調(diào)用
/** * description:http輔助工具類 * * @author RenShiWei * Date: 2021/5/7 22:11 **/ public class HttpHelper { /** * description:從request獲取body的json數(shù)據(jù) * * @param request / * @return / * @author RenShiWei * Date: 2021/5/7 22:44 */ public static String getBodyString(ServletRequest request) { StringBuilder sb = new StringBuilder(); ServletInputStream inputStream = null; BufferedReader reader = null; try { inputStream = request.getInputStream(); reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); String line = ""; while ((line = reader.readLine()) != null) { sb.append(line); } } catch (IOException e) { e.printStackTrace(); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (reader != null) { try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } } return sb.toString(); } /** * description:從request獲取body的json數(shù)據(jù),并格式化成map形式 * * @param request / * @return / * @author RenShiWei * Date: 2021/5/7 22:44 */ @SuppressWarnings("all") public static Map<String, Object> getBodyMap(ServletRequest request) { Map<String, Object> params = new HashMap<>(); String bodyString = getBodyString(request); if (StrUtil.isNotEmpty(bodyString)) { params = JSON.parseObject(bodyString, Map.class); } return params; } }
4.在SpringBoot啟動(dòng)類上添加@ServletComponentScan注解
使用
@RestController @RequestMapping("/api/test") public class TestController { @GetMapping("/test1") @AnonymousAccess public ResponseEntity<String> test1(HttpServletRequest request) { System.out.println("---GET請(qǐng)求 getParameterNames 入?yún)?--"); try { request.setCharacterEncoding("utf-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } Enumeration<String> parameterNames = request.getParameterNames(); while (parameterNames.hasMoreElements()) { String paraName = parameterNames.nextElement(); System.out.println(paraName + ": " + request.getParameter(paraName)); } System.out.println("---GET請(qǐng)求 getParameterMap 入?yún)?--"); Map<String, String[]> map = request.getParameterMap(); Set<Map.Entry<String, String[]>> keSet = map.entrySet(); for (Iterator<Map.Entry<String, String[]>> itr = keSet.iterator(); itr.hasNext(); ) { Map.Entry<String, String[]> me = itr.next(); Object ok = me.getKey(); Object ov = me.getValue(); String[] value = new String[1]; if (ov != null) { value = (String[]) ov; } else { value[0] = ov.toString(); } for (int k = 0; k < value.length; k++) { System.out.println(ok + "=" + value[k]); } } return ResponseEntity.ok(null); } @PostMapping("/test2") @AnonymousAccess public ResponseEntity<String> test2(@RequestBody TestParam testParam, HttpServletRequest request) { String body = HttpHelper.getBodyString(request); Map<String, Object> bodyMap = HttpHelper.getBodyMap(request); System.out.println("body: " + body); System.out.println("bodyMap: " + bodyMap); return ResponseEntity.ok(null); } }
postman測(cè)試
GET請(qǐng)求
POST的JSON格式參數(shù)(其他方式結(jié)果與GET請(qǐng)求結(jié)果一致)
結(jié)果
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot如何配置Controller實(shí)現(xiàn)Web請(qǐng)求處理
這篇文章主要介紹了SpringBoot如何配置Controller實(shí)現(xiàn)Web請(qǐng)求處理,文中通過(guò)圖解示例介紹的很詳細(xì),具有有一定的參考價(jià)值,需要的小伙伴可以參考一下2023-05-05Mybatis 查詢語(yǔ)句條件為枚舉類型時(shí)報(bào)錯(cuò)的解決
這篇文章主要介紹了Mybatis 查詢語(yǔ)句條件為枚舉類型時(shí)報(bào)錯(cuò)的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01Java并發(fā)容器相關(guān)知識(shí)總結(jié)
今天給大家?guī)?lái)的文章是Java并發(fā)容器的相關(guān)知識(shí),文中有非常詳細(xì)的介紹,對(duì)正在學(xué)習(xí)Java并發(fā)容器的小伙伴們很有幫助,需要的朋友可以參考下2021-06-06Java中實(shí)現(xiàn)二叉樹(shù)的遍歷與重構(gòu)
這篇文章主要介紹了Java中實(shí)現(xiàn)二叉樹(shù)的遍歷與重構(gòu),樹(shù)是一種非線性的數(shù)據(jù)結(jié)構(gòu),它是由n(n>=0)個(gè)有限結(jié)點(diǎn)組成一個(gè)具有層次關(guān)系的集合,把它叫做樹(shù)是因?yàn)樗雌饋?lái)像一棵倒掛的樹(shù),也就是說(shuō)它是根朝上,而葉朝下的,需要的朋友可以參考下2023-10-10