1. 程式人生 > 程式設計 >SpringBoot過濾器如何獲取POST請求的JSON引數

SpringBoot過濾器如何獲取POST請求的JSON引數

目錄
  • SpringBoot過濾器獲取POST請求的ON引數
    • 想到了使用過濾器來實現這個功能
    • 所以我們可以通過獲取到輸入流來獲取bodwww.cppcns.comy
    • 從原始碼我們可以看到
    • 我們建立一個類並繼承這個包裝類
    • 有一點需要注意的

SpringBoot過濾器獲取POST請求的JSON引數

專案中需要將每個請求的路徑和請求引數以及響應結果,都記錄在日誌中,這樣在出現問題時可以快速定位是哪裡出現了問題。

想到了使用過濾器來實現這個功能

當請求來到過濾器時,會有一個Request引數,通過該引數就能獲取到請求路徑和請求引數,以及相關內容

parameterMap = httpRequest.getParameterMap();
String requestMethod = httpRequest.getMethod();
String remoteAddr = httpRequest.getRemoteAddr();
int remotePort = httpRequest.getRemotePort();

上面的getParameterMap(),只能夠獲取到GET請求的引數,如果是POST方法傳的JSON那就沒法獲取到,那如何獲取呢,POST的請求是在請求體body中,而POST請求中的body引數是已流形式存在的

所以我們可以通過獲取到輸入流來獲取body

ServletInputStream inputStream = httpRequest.getInputStream();
InputStreamReader reader = new InputStreamReader(inputStream,StandardCharsets.UTF_8);
BufferedReader bfReader = new BufferedReader(reader);
StringBuilder sb = new StringBuilder();
String line;
while ((line = bfReader.readLine()) != null){
sb.append(line);
}
System.out.println(sb.toString());

通過上面的方法,我們確實能在過濾器中獲取到POST的JSON引數了,但是按照上面的方法實現的過濾器,我們會發現,當請求經過過濾器來到Controller的時候,請求引數不見了

image-20201219133609844

可以看到,過濾器確實拿到JSON引數,但是接著報了一個request body missing的異常,也就是請求來到Controller時,引數沒有了,這是為啥呢?我們先去原始碼看看,Controller平時是怎麼拿到請求引數的吧

image-20201219134042794

根據DeBug,可以看到SpringBoot處理請求的最主要的兩個方法是上圖紅框的doServicedoDisparch方法,上面就是通過反射去獲取引數名去匹配等

SpringBoot過濾器如何獲取POST請求的JSON引數

來到invokeForRequest

方法,這裡面的getMethodArgumentValues,就是SpringBoot獲取請求引數的入口,進入入口後

image-20201219135249418

再經過上面的紅框,就能看到SpringBoot獲取POST請求JSON的引數的真面目了

image-20201219135426586

從原始碼我們可以看到

SpringBoot也是通過獲取request的輸入流來獲取引數,這樣上面的疑問就能解開了,為什麼經過過濾器來到Controller請求引數就沒了,這是因為 InputStream read方法內部有一個,postion,標誌當前流讀取到的位置,每讀取一次,位置就會移動一次,如果讀到最後,InputStream.read方法會返回-1,標誌已經讀取完了,如果想再次讀取,可以呼叫inputstream.reset方法,position就會移動到上次呼叫mark的位置,mark預設是0,所以就能從頭再讀了。但是呢 是否能reset又是由markSupported決定的,為true能reset,為false就不能reset,從原始碼可以看到,markSupported是為false的,而且一呼叫reset就是直接異常

image-20201219140007286

所以這也就代表,InputStream只能被讀取一次,後面就讀取不到了。因此我們在過濾器的時候,已經將InputStream讀取過了一次,當來到Controller,SpringBoot讀取InputStream的時候自然是什麼都讀取不到了

image-20201219140830451

既然InputStream只能讀取一次,那我們可以把InputStream給儲存下來,然後完整的傳下去SpringBoot就可以讀取到了,這裡就需要用到HttpServletRequest的包裝類HttpServletRequestWrapper了,該類可以自定義一些方法

我們建立一個類並繼承這個包裝類

public class RequestWrapper extends HttpServletRequestWrapper {
    private final byte[] body;
    public RequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        //儲存一份InputStream,將其轉換為位元組陣列
        body = StreamUtils.copyToByteArray(request.getInputStream());
    }
	//轉換成String
    public String getBodyString(){
        return new String(body,StandardCharsets.UTF_8);
    }
    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }
	//把儲存好的InputStream,傳下去
    @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
 http://www.cppcns.com           public boolean isFinished() {
                return false;
            }
            @Override
            public boolean isReady() {
                return false;
            }
            @Override
            public void setReadListener(ReadListener readListener) {
            }
        };
    }
}

通過儲存一份流,就可實現在過濾器中能拿到JSON引數,同時Controller也不會丟失引數

image-20201219141929107

有一點需要注意的

在過濾器放行的時候,放行的是包裝類和而不是原來的Request

image-20201219142124831

以上為個人經驗,希望能給大家一個參考,也希望大家多多支援我們。