SpringBoot如何配置獲取request中body的json格式參數(shù)
背景
最近開發(fā)項目,因為有第三方調(diào)用我們的接口,我們使用SpringBoot以JavaBean的方式接收了我們預期的參數(shù),參數(shù)接收也沒有什么異常。但是有一些需求問題需要溝通,需要拿到合作第三方傳入的所有參數(shù),來進行參數(shù)核驗。
如何拿到請求的所有參數(shù)呢?正常的思路肯定是從request中獲取,如果是GET請求,參數(shù)在請求路徑中拼接;如果是POST請求,參數(shù)在request的請求體(body)中。
一番檢索,很容易拿到相關(guān)代碼。但是經(jīng)過實操,發(fā)現(xiàn)并不能如期獲取到參數(shù)。經(jīng)過思考,我的接口是POST請求,參數(shù)形式是json格式(使用了@RequestBody來修飾參數(shù))。
具體過程參看如下分析
獲取請求中的參數(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)過測試以上兩個方法可以獲取GET請求的參數(shù),以及參數(shù)格式為form-data、x-www-form-urlencoded的POST請求,但是json格式參數(shù)(postman中為raw)的參數(shù)不能獲得。
根據(jù)代碼的簡介程度,選擇方法一,明顯更舒服一些。
以上結(jié)論經(jīng)過postman實測.
獲取POST請求json格式的參數(shù)
以上方法已經(jīng)可以獲取大多數(shù)情況下的請求的參數(shù),但是明顯還不能滿足需求,需要獲取傳入json格式的參數(shù)。
經(jīng)過檢索推薦方法(參看后邊完整方法)
經(jīng)過一番檢索,網(wǎng)上推薦的方法一般都是使用流來進行參數(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);
遇到的問題及解決思路
問題1 流不能多次被調(diào)用
但是又會遇到如下問題:
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ù)報錯信息分析簡單來說,就是getInputStream()已經(jīng)被調(diào)用了,不能再次調(diào)用??墒俏铱创a上,我也沒調(diào)用。經(jīng)過一番檢索,原來@RequestBody注解配置后,默認會使用流來讀取數(shù)據(jù)。
具體原因:
- 默認配置時,getInputStream()和getReader()一起使用會報錯,使用兩遍getInputStream(),第二遍會為空。
- 當存在@RequestBody等注解時,springMVC已讀取過一遍流,默認單獨使用getInputStream()或getReader()都為空。
實測,不加@RequestBody注解,可以如期獲得請求中的json參數(shù),但是又不得不加@RequestBody注解。這樣就需要新的思路
解決思路
寫filter繼承HttpServletRequestWrapper,緩存InputStream,覆蓋getInputStream()和getReader()方法,使用ByteArrayInputStream is = new ByteArrayInputStream(body.getBytes());讀取InputStream。
實現(xiàn)方法
1.定義增強類,繼承繼承HttpServletRequestWrapper
將請求體中的流copy一份,覆寫getInputStream()和getReader()方法供外部使用。每次調(diào)用覆寫后的getInputStream()方法都是從復制出來的二進制數(shù)組中進行獲取,這個二進制數(shù)組在對象存在期間一直存在,這樣就實現(xiàn)了流的重復讀取。
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)建過濾器
@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("過濾器包裝request失敗!將返回原來的request"); chain.doFilter(request, response); } else { log.info("過濾器包裝request成功"); chain.doFilter(requestWrapper, response); } } @Override public void destroy() { } }
注意事項:
- 使用@WebFilter注解的urlPatterns屬性,可指定過濾哪一個接口方法。
- 過濾器類最好不要交由Spring管理,否則每一個接口都會進行過濾(實測)。例如:不要添加@Component注解. 3.編寫工具類方便調(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啟動類上添加@ServletComponentScan注解
使用
@RestController @RequestMapping("/api/test") public class TestController { @GetMapping("/test1") @AnonymousAccess public ResponseEntity<String> test1(HttpServletRequest request) { System.out.println("---GET請求 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請求 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測試
GET請求
POST的JSON格式參數(shù)(其他方式結(jié)果與GET請求結(jié)果一致)
結(jié)果
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot如何配置Controller實現(xiàn)Web請求處理
這篇文章主要介紹了SpringBoot如何配置Controller實現(xiàn)Web請求處理,文中通過圖解示例介紹的很詳細,具有有一定的參考價值,需要的小伙伴可以參考一下2023-05-05