最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot項目中HTTP請求體只能讀一次的解決方案

 更新時間:2024年08月09日 11:54:56   作者:小明同學(xué)的學(xué)長  
在基于Spring開發(fā)Java項目時,可能需要重復(fù)讀取HTTP請求體中的數(shù)據(jù),例如使用攔截器打印入?yún)⑿畔⒌?但當(dāng)我們重復(fù)調(diào)用getInputStream()或者getReader()時,通常會遇到SpringBoot HTTP請求只讀一次的問題,本文給出了幾種解決方案,需要的朋友可以參考下

問題描述

在基于Spring開發(fā)Java項目時,可能需要重復(fù)讀取HTTP請求體中的數(shù)據(jù),例如使用攔截器打印入?yún)⑿畔⒌龋?dāng)我們重復(fù)調(diào)用getInputStream()或者getReader()時,通常會遇到類似以下的錯誤信息:

大體的意思是當(dāng)前request的getInputStream()已經(jīng)被調(diào)用過了。那為什么會出現(xiàn)這個問題呢?

原因分析

主要原因有兩個,一是Java自身的設(shè)計中,InputStream作為數(shù)據(jù)管道本身只支持讀取一次,如果要支持重復(fù)讀取的話就需要重新初始化;二是Servlet容器中Request的實現(xiàn)問題,我們以默認(rèn)的Tomcat為例,可以發(fā)現(xiàn)在Request有兩個boolean類型的屬性,分別是usingReader和usingInputStream,當(dāng)調(diào)用getInputStream()或getReader()時會分別檢查兩個屬性的值,并在執(zhí)行后將對應(yīng)的屬性設(shè)置為true,如果在檢查時變量的值已經(jīng)為true了,那么就會報出以上錯誤信息。

解決方案

不太可行的方案:簡單粗暴的反射機制

涉及到變量的修改,我們首先想到的就是有沒有提供方法進行修改,不過可惜的是usingReader和usingInputStream并未提供,所以想要在使用過程中修改這兩個屬性估計只能靠反射了,在使用過程中每次調(diào)用后通過反射將usingReader和usingInputStream設(shè)置為false,每次根據(jù)讀取出的內(nèi)容把數(shù)據(jù)流初始化回去,理論上就可以再次讀取了。

首先說反射機制本身就是通過破壞類的封裝來實現(xiàn)動態(tài)修改的,有點過于粗暴了,其次也是主要原因,我們只能針對我們自己實現(xiàn)的代碼進行處理,框架本身如果調(diào)用getInputStream()和getReader()的話,我們就沒法通過這個辦法干預(yù)了,所以這個方案在給予Spring的Web項目中并不可行。

理論上可行的方案:HttpServletRequest接口

HttpServletRequest是一個接口,理論上我們只需要創(chuàng)建一個實現(xiàn)類就可以自定義getInputStream()和getReader()的行為,自然也就能解決RequestBody不能重復(fù)讀取的問題,但這個方案的問題在于HttpServletRequest有70個方法,而我們只需要修改其中兩個而已,通過這種方式去解決有點得不償失。

部分場景可行的方案:ContentCachingRequestWrapper

Spring本身提供了一個Request包裝類來處理重復(fù)讀取的問題,即ContentCachingRequestWrapper,其實現(xiàn)思路就是在讀取RequestBody時將內(nèi)存緩存到它內(nèi)部的一個字節(jié)流中,后續(xù)讀取可以通過調(diào)用getContentAsString()或getContentAsByteArray()獲取到緩存下來的內(nèi)容。

之所以說這個方案是部分場景可行主要是兩個方面,一是ContentCachingRequestWrapper沒有重寫getInputStream()和getReader()方法,所以框架中使用這兩個方法的地方依然獲取不到緩存下來的內(nèi)容,僅支持自定義的業(yè)務(wù)邏輯;第二點和第一點有所關(guān)聯(lián),因為其沒有修改getInputStream()和getReader()方法,所以我們在使用時只能在使用RequestBody注解后使用ContentCachingRequestWrapper,否則就會出現(xiàn)RequestBody注解修飾的參數(shù)無法正常讀取請求體的問題,也就限定了它的使用范圍如下圖所示:

如果僅需要在業(yè)務(wù)代碼后再次讀取請求體內(nèi)容,那么使用ContentCachingRequestWrapper也足以滿足需求,具體使用方法請參考下一節(jié)的說明。

目前的最佳實踐:繼承HttpServletRequestWrapper

之前我們提到實現(xiàn)HttpServletRequest需要實現(xiàn)70個方法,所以不太可能自行實現(xiàn),這個方案算是進階版本,繼承HttpServletRequest的實現(xiàn)類,之后再自定義我們需要修改的兩個方法。

HttpServletRequest作為一個接口,肯定會有其實現(xiàn)去支撐它的業(yè)務(wù)功能,因為Servlet容器的選擇較多,我們也不能使用某一方提供的實現(xiàn),所以選擇的范圍也就被限制到了Java EE(現(xiàn)在叫Jakarta EE)標(biāo)準(zhǔn)范圍內(nèi),通過查看HttpServletRequest的實現(xiàn),可以發(fā)現(xiàn)在標(biāo)準(zhǔn)內(nèi)提供了一個包裝類:HttpServletRequestWrapper,我們的方案也是圍繞它展開。

思路簡述

  • 自定義子類,繼承HttpServletRequestWrapper,在子類的構(gòu)造方法中將RequestBody緩存到自定義的屬性中。
  • 自定義getInputStream()和getReader()的業(yè)務(wù)邏輯,不再校驗usingReader和usingInputStream,且在調(diào)用時讀取緩存下來的內(nèi)容。
  • 自定義Filter,將默認(rèn)的HttpServletRequest替換為自定義的包裝類。

代碼展示

  • 繼承HttpServletRequestWrapper,實現(xiàn)子類CustomRequestWrapper,并自定義getInputStream()和getReader()的業(yè)務(wù)邏輯
// 1.繼承HttpServletRequestWrapper
public class CustomRequestWrapper extends HttpServletRequestWrapper {

    // 2.定義final屬性,用于緩存請求體內(nèi)容
    private final byte[] content;

    public CustomRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        // 3.構(gòu)造方法中將請求體內(nèi)容緩存到內(nèi)部屬性中
        this.content = StreamUtils.copyToByteArray(request.getInputStream());
    }

    // 4.重新getInputStream()
    @Override
    public ServletInputStream getInputStream() {
        // 5.將緩存下來的內(nèi)容轉(zhuǎn)換為字節(jié)流
        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(content);
        return new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener listener) {

            }

            @Override
            public int read() {
                // 6.讀取時讀取第5步初始化的字節(jié)流
                return byteArrayInputStream.read();
            }
        };
    }

    // 7.重寫getReader()方法,這里復(fù)用getInputStream()的邏輯
    @Override
    public BufferedReader getReader() {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }
}
  • 自定義Filter將默認(rèn)的HttpServletRequest替換為自定義的CustomRequestWrapper
// 1.實現(xiàn)Filter接口,此處也可以選擇繼承HttpFilter
public class RequestWrapperFilter implements Filter {
    // 2. 重寫或?qū)崿F(xiàn)doFilter方法
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 3.此處判斷是為了縮小影響范圍,本身CustomRequestWrapper只是針對HttpServletRequest,不進行判斷可能會影響其他類型的請求
        if (request instanceof HttpServletRequest) {
            // 4.將默認(rèn)的HttpServletRequest轉(zhuǎn)換為自定義的CustomRequestWrapper
            CustomRequestWrapper requestWrapper = new CustomRequestWrapper((HttpServletRequest) request);
            // 5.將轉(zhuǎn)換后的request傳遞至調(diào)用鏈中
            chain.doFilter(requestWrapper, response);
        } else {
            chain.doFilter(request, response);
        }
    }
}
  • 將Filter注冊到Spring容器,這一步可以通過多種方式執(zhí)行,這里采用比較傳統(tǒng)但比較靈活的Bean方式注冊,如果圖方便可以通過ServletComponentScan注解+ WebFilter注解的方式。
/**
 * 過濾器配置,支持第三方過濾器
 */
@Configuration
public class FilterConfigure {
    /**
     * 請求體封裝
     * @return
     */
    @Bean
    public FilterRegistrationBean<RequestWrapperFilter> filterRegistrationBean(){
        FilterRegistrationBean<RequestWrapperFilter> bean = new FilterRegistrationBean<>();
        bean.setFilter(new RequestWrapperFilter());
        bean.addUrlPatterns("/*");
        return bean;
    }
}

至此我們就可以在項目中重復(fù)讀取請求體了,如果選擇使用Spring提供的ContentCachingRequestWrapper,那么在Filter中將CustomRequestWrapper替換為ContentCachingRequestWrapper即可,不過需要注意在上一節(jié)提到的可用范圍較小的問題。

以上就是SpringBoot項目中HTTP請求體只能讀一次的解決方案的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot HTTP請求只讀一次的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Spring Boot 日志配置方法(超詳細(xì))

    Spring Boot 日志配置方法(超詳細(xì))

    默認(rèn)情況下,Spring Boot會用Logback來記錄日志,并用INFO級別輸出到控制臺。下面通過本文給大家介紹Spring Boot 日志配置方法詳解,感興趣的朋友參考下吧
    2017-07-07
  • Maven如何構(gòu)建可執(zhí)行的jar包(包含依賴jar包)

    Maven如何構(gòu)建可執(zhí)行的jar包(包含依賴jar包)

    這篇文章主要介紹了Maven如何構(gòu)建可執(zhí)行的jar包(包含依賴jar包) ,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-11-11
  • Java變量常量聲明和定義原理解析

    Java變量常量聲明和定義原理解析

    這篇文章主要介紹了Java變量常量聲明和定義原理解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-01-01
  • Android中的LinearLayout布局

    Android中的LinearLayout布局

    在一般情況下,當(dāng)有很多控件需要在一個界面列出來時,我們就可以使用線性布局(LinearLayout)了,線性布局是按照垂直方向(vertical)或水平方向(horizontal)的順序依次排序子元素,每一個子元素都位于前一個元素之后,下面我們就簡單的了解一下吧
    2017-01-01
  • Springboot POI導(dǎo)出Excel(瀏覽器)

    Springboot POI導(dǎo)出Excel(瀏覽器)

    這篇文章主要為大家詳細(xì)介紹了Springboot POI導(dǎo)出Excel,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-05-05
  • Java中的Kafka消費者詳解

    Java中的Kafka消費者詳解

    這篇文章主要介紹了Java中的Kafka消費者詳解,Kafka是一個分布式流行消息系統(tǒng),通常用于大規(guī)模數(shù)據(jù)處理和實時數(shù)據(jù)流應(yīng)用程序,它具有高吞吐量、可擴展性和容錯性的特點,需要的朋友可以參考下
    2023-09-09
  • Java生成遞增流水號(編號+時間+流水號)簡單示例

    Java生成遞增流水號(編號+時間+流水號)簡單示例

    這篇文章主要給大家介紹了關(guān)于Java生成遞增流水號(編號+時間+流水號)的相關(guān)資料,在開發(fā)項目漫長的過程中常常會遇到流水號需要自動生成的問題存在,文中給出了詳細(xì)的代碼示例,需要的朋友可以參考下
    2023-07-07
  • java編程兩種樹形菜單結(jié)構(gòu)的轉(zhuǎn)換代碼

    java編程兩種樹形菜單結(jié)構(gòu)的轉(zhuǎn)換代碼

    這篇文章主要介紹了java編程兩種樹形菜單結(jié)構(gòu)的轉(zhuǎn)換代碼,首先介紹了兩種樹形菜單結(jié)構(gòu)的代碼,然后展示了轉(zhuǎn)換器實例代碼,最后分享了相關(guān)實例及結(jié)果演示,具有一定借鑒價值,需要的朋友可以了解下。
    2017-12-12
  • 詳解Java反射創(chuàng)建對象

    詳解Java反射創(chuàng)建對象

    今天帶大家學(xué)習(xí)Java的基礎(chǔ)知識,文中對Java反射創(chuàng)建對象作了非常詳細(xì)的介紹及代碼示例,對正在學(xué)習(xí)Java的小伙伴們很有幫助,需要的朋友可以參考下
    2021-05-05
  • springboot配置文件的加載順序解析

    springboot配置文件的加載順序解析

    這篇文章主要介紹了springboot配置文件的加載順序解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-02-02

最新評論

新兴县| 皋兰县| 兰州市| 尤溪县| 凤凰县| 历史| 韶山市| 阿拉善右旗| 平昌县| 贵州省| 和田市| 嵊泗县| 纳雍县| 阿鲁科尔沁旗| 甘洛县| 谷城县| 贵州省| 平乐县| 高州市| 色达县| 栖霞市| 如皋市| 怀来县| 革吉县| 青海省| 盐山县| 泽州县| 石渠县| 勃利县| 普陀区| 弥勒县| 遵义县| 木兰县| 淳安县| 墨竹工卡县| 自治县| 西林县| 莱阳市| 额敏县| 和政县| 深圳市|