SpringBoot如何配置獲取request中body的json格式參數(shù)
背景
最近開發(fā)項(xiàng)目,因?yàn)橛械谌秸{(diào)用我們的接口,我們使用SpringBoot以JavaBean的方式接收了我們預(yù)期的參數(shù),參數(shù)接收也沒有什么異常。但是有一些需求問題需要溝通,需要拿到合作第三方傳入的所有參數(shù),來進(jìn)行參數(shù)核驗(yàn)。
如何拿到請求的所有參數(shù)呢?正常的思路肯定是從request中獲取,如果是GET請求,參數(shù)在請求路徑中拼接;如果是POST請求,參數(shù)在request的請求體(body)中。
一番檢索,很容易拿到相關(guān)代碼。但是經(jīng)過實(shí)操,發(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)過測試以上兩個(gè)方法可以獲取GET請求的參數(shù),以及參數(shù)格式為form-data、x-www-form-urlencoded的POST請求,但是json格式參數(shù)(postman中為raw)的參數(shù)不能獲得。
根據(jù)代碼的簡介程度,選擇方法一,明顯更舒服一些。
以上結(jié)論經(jīng)過postman實(shí)測.
獲取POST請求json格式的參數(shù)
以上方法已經(jīng)可以獲取大多數(shù)情況下的請求的參數(shù),但是明顯還不能滿足需求,需要獲取傳入json格式的參數(shù)。
經(jīng)過檢索推薦方法(參看后邊完整方法)
經(jīng)過一番檢索,網(wǎng)上推薦的方法一般都是使用流來進(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);
遇到的問題及解決思路
問題1 流不能多次被調(diào)用
但是又會(huì)遇到如下問題:
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ò)信息分析簡單來說,就是getInputStream()已經(jīng)被調(diào)用了,不能再次調(diào)用。可是我看代碼上,我也沒調(diào)用。經(jīng)過一番檢索,原來@RequestBody注解配置后,默認(rèn)會(huì)使用流來讀取數(shù)據(jù)。
具體原因:
- 默認(rèn)配置時(shí),getInputStream()和getReader()一起使用會(huì)報(bào)錯(cuò),使用兩遍getInputStream(),第二遍會(huì)為空。
- 當(dāng)存在@RequestBody等注解時(shí),springMVC已讀取過一遍流,默認(rèn)單獨(dú)使用getInputStream()或getReader()都為空。
實(shí)測,不加@RequestBody注解,可以如期獲得請求中的json參數(shù),但是又不得不加@RequestBody注解。這樣就需要新的思路
解決思路
寫filter繼承HttpServletRequestWrapper,緩存InputStream,覆蓋getInputStream()和getReader()方法,使用ByteArrayInputStream is = new ByteArrayInputStream(body.getBytes());讀取InputStream。
實(shí)現(xiàn)方法
1.定義增強(qiáng)類,繼承繼承HttpServletRequestWrapper
將請求體中的流copy一份,覆寫getInputStream()和getReader()方法供外部使用。每次調(diào)用覆寫后的getInputStream()方法都是從復(fù)制出來的二進(jìn)制數(shù)組中進(jìn)行獲取,這個(gè)二進(jìn)制數(shù)組在對象存在期間一直存在,這樣就實(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)建過濾器
@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() {
}
}
注意事項(xiàng):
- 使用@WebFilter注解的urlPatterns屬性,可指定過濾哪一個(gè)接口方法。
- 過濾器類最好不要交由Spring管理,否則每一個(gè)接口都會(huì)進(jìn)行過濾(實(shí)測)。例如:不要添加@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啟動(dòng)類上添加@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é)果

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot如何配置Controller實(shí)現(xiàn)Web請求處理
這篇文章主要介紹了SpringBoot如何配置Controller實(shí)現(xiàn)Web請求處理,文中通過圖解示例介紹的很詳細(xì),具有有一定的參考價(jià)值,需要的小伙伴可以參考一下2023-05-05
Mybatis 查詢語句條件為枚舉類型時(shí)報(bào)錯(cuò)的解決
這篇文章主要介紹了Mybatis 查詢語句條件為枚舉類型時(shí)報(bào)錯(cuò)的解決方案,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01
Java并發(fā)容器相關(guān)知識(shí)總結(jié)
今天給大家?guī)淼奈恼率荍ava并發(fā)容器的相關(guān)知識(shí),文中有非常詳細(xì)的介紹,對正在學(xué)習(xí)Java并發(fā)容器的小伙伴們很有幫助,需要的朋友可以參考下2021-06-06
Java中實(shí)現(xiàn)二叉樹的遍歷與重構(gòu)
這篇文章主要介紹了Java中實(shí)現(xiàn)二叉樹的遍歷與重構(gòu),樹是一種非線性的數(shù)據(jù)結(jié)構(gòu),它是由n(n>=0)個(gè)有限結(jié)點(diǎn)組成一個(gè)具有層次關(guān)系的集合,把它叫做樹是因?yàn)樗雌饋硐褚豢玫箳斓臉?也就是說它是根朝上,而葉朝下的,需要的朋友可以參考下2023-10-10

