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

SpringMVC多線程下無法獲取請(qǐng)求的原因和解決方法

 更新時(shí)間:2024年04月22日 08:54:22   作者:毅航  
從問題出發(fā),逐步講透在SpringMVC中使用RequestContextHolder對(duì)象在多線程情況下無法獲取請(qǐng)求的真實(shí)原因,本文將和大家一起深入剖析SpringMVC多線程下無法獲取請(qǐng)求的原因和解決,需要的朋友可以參考下

前言

眾所周知,在SpringMVC中如果我們期待獲取當(dāng)前請(qǐng)求的HttpServletRequest對(duì)象,通常有如下幾種方式:

  • 通過方法參數(shù)注入:在Controller的方法中,可以直接聲明HttpServletRequest類型的參數(shù),Spring MVC會(huì)自動(dòng)將當(dāng)前請(qǐng)求的HttpServletRequest對(duì)象注入進(jìn)來。例如:
@Controller
public class MyController {

    @RequestMapping("/example")
    public String handleRequest(HttpServletRequest request) {
        // 使用request對(duì)象
        return "example";
    }
}
  • 通過RequestContextHolderSpring MVC提供了一個(gè)RequestContextHolder類,例如:
HttpServletRequest request = ((ServletRequestAttributes) 
RequestContextHolder.getRequestAttributes()).getRequest();

而本次我們重點(diǎn)分析當(dāng)使用RequestContextHolder在多線程環(huán)境下獲取請(qǐng)求所可能導(dǎo)致的一些問題。

(注:在獲取當(dāng)前請(qǐng)求的HttpServletRequest我們會(huì)利用RequestContextHolder先獲取到ServletRequestAttributes后,再通過ServletRequestAttributes來獲取對(duì)應(yīng)的httpServletRequest對(duì)象)

問題復(fù)現(xiàn)

為了直觀的理解RequestContextHolder在多線程使用下所導(dǎo)致的問題,我們先來通過一個(gè)業(yè)務(wù)中的真實(shí)場(chǎng)景來進(jìn)行分析。

在國(guó)際化功能開發(fā)中,我們通常會(huì)將用戶當(dāng)前的語言信息存放在Request請(qǐng)求中,這樣后端通過獲取請(qǐng)求頭的中的語言信息就能成功獲取到用戶所支持的語言。

進(jìn)一步,對(duì)于一些涉及到國(guó)際化的導(dǎo)入,導(dǎo)出的耗時(shí)操作來說,我們通常會(huì)將其放在異步線程中進(jìn)行執(zhí)行,以提升程序性能。代碼邏輯大致如下:

@GetMapping("/missing-request-header")
public String getMissingRequestHeader() {
    // 主線程獲取請(qǐng)求頭信息
    String mainThreadLanguages = ServletUtils.getLanguagesExistProblem();
    log.info("主線程獲取請(qǐng)求頭信息:{}", mainThreadLanguages);
    new Thread(() -> {
        // 子線程獲取請(qǐng)求頭信息 模擬執(zhí)行耗時(shí)操作
        String subThreadLanguages = ServletUtils.getLanguagesExistProblem();
        log.info("子線程獲取請(qǐng)求頭信息:{}", subThreadLanguages);
       
    }).start();
    return "success";
}

上述程序的邏輯相對(duì)來說比較簡(jiǎn)單,唯一可能讓你困惑的可能在于ServletUtils.getLanguagesExistProblem()方法的調(diào)用。

該方法是筆者所寫的一個(gè)工具類,其主要作用就是獲取當(dāng)前請(qǐng)求頭中的Lang屬性,方法內(nèi)部具體邏輯如下所示:

    /**
     * 獲取客戶端請(qǐng)求頭中的語言信息。
     * 其會(huì)從當(dāng)前的HTTP請(qǐng)求中提取客戶端所設(shè)置的語言信息。
     * 主要通過讀取請(qǐng)求頭中的"X_CLIENT_LANG"字段來獲取客戶端語言偏好。
     *
     * @return String 客戶端請(qǐng)求頭中指定的語言信息。
     * 如果不存在該字段則返回默認(rèn)的zh-cn。
     */
    public static String  getLanguagesExistProblem() {
        HttpServletRequest request = getRequest();
        Assert.notNull(request);

        String lang =  request.getHeader(X_CLIENT_LANG);

        if (StrUtil.isNotBlank(lang)) {
            return lang;
        }

        return "zh-cn";
    }

可以看到,在getLanguagesExistProblem方法內(nèi)部又會(huì)通過getRequest獲取到當(dāng)前請(qǐng)求的HttpServletRequest信息,而getRequest內(nèi)部邏輯如下所示:

public static HttpServletRequest getRequest() {
    HttpServletRequest httpServletRequest = null;
    try {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (requestAttributes instanceof ServletRequestAttributes) {
            ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
            httpServletRequest = servletRequestAttributes.getRequest();
        }
    } catch (Exception e) {
        // 記錄異常,但不向外拋出,以避免可能的業(yè)務(wù)邏輯中斷
        log.error("獲取HttpServletRequest時(shí)發(fā)生異常:", e);
    }

    // 返回獲取到的請(qǐng)求對(duì)象,如果失敗則返回null
    return httpServletRequest;
}

整體來看上述代碼調(diào)用邏輯如下:

至此,相信你對(duì)于示例代碼的邏輯其實(shí)已經(jīng)清楚了。其無非就是會(huì)首先會(huì)在主線程中獲取當(dāng)前請(qǐng)求頭中的語言信息,接著,又會(huì)新建一個(gè)子線程嘗試去獲取到請(qǐng)求頭中的語言信息??雌饋硭坪醮a似乎沒什么問題,嘗試執(zhí)行代碼,你會(huì)發(fā)現(xiàn)有如下提示:

追蹤溯源

通過錯(cuò)誤提示不難發(fā)現(xiàn)是子線程在調(diào)用getLanguagesExistProblem()方法時(shí)所提示的錯(cuò)誤。具體來看,是因?yàn)槠鋬?nèi)部Assert.notNull(request);斷言所提示的錯(cuò)誤,而導(dǎo)致問題發(fā)生的原因是因?yàn)閭魅氲?code>request對(duì)象為null,進(jìn)而導(dǎo)致不滿足斷言條件notNull從而提示異常信息。

正如我們前面所說,我們的request對(duì)象是通過RequestContextHolder來獲取的。具體我們的代碼來看,其本質(zhì)是通過ServletRequestAttributesgetRequest來完成這一操作。那為什么會(huì)在多線程情況下有這樣的問題呢?初看這一問題你可能會(huì)有點(diǎn)摸不到頭腦,不知該如何下手。沒有思路也別慌,接下來,不妨聽一聽筆者是如何對(duì)這一問題進(jìn)行分析的。

首先,既然RequestContextHolder可以實(shí)現(xiàn)獲取當(dāng)前請(qǐng)求的功能,其一定會(huì)把請(qǐng)求進(jìn)行一層緩存,以確保我們無論在程序的任何位置都能獲取到請(qǐng)求,順著這一思路,如果要你來實(shí)現(xiàn)這一需求,你會(huì)如何設(shè)計(jì)呢?

我想你大概率會(huì)將此處的邏輯設(shè)計(jì)在程序公共的入口位置,那SpringMVC中請(qǐng)求第一次進(jìn)入時(shí)公共的會(huì)首先在哪處理呢?顯示是Servlet中的service方法。具體到DispatcherServlet來看,其內(nèi)部邏輯如下所示:

@Override
protected void service(HttpServletRequest request, 
                        HttpServletResponse response)
        {

      // ... 省略其他無關(guān)代碼
 
     // 處理請(qǐng)求核心代碼,點(diǎn)擊該方法進(jìn)入
     processRequest(request, response); 
 
}

(Ps: 這里需要讀者有一點(diǎn)Servlet相關(guān)知識(shí),簡(jiǎn)單來看,所有請(qǐng)求進(jìn)入Servlet后都會(huì)先通過Service方法的處理~~~)

不難發(fā)現(xiàn),service方法其內(nèi)部的核心邏輯會(huì)委托processRequest進(jìn)行處理,而processRequest其內(nèi)部邏輯如下:

protected final void processRequest(HttpServletRequest request, 
    // ... 省略其他無關(guān)邏輯
    // 初始化ContextHolders, 便于訪問上下文信息
    initContextHolders(request, localeContext, requestAttributes);
  
    doService(request, response);
  }

通過initContextHolders方法的名稱我們不難猜出,其大概的作用微在于初始化一個(gè)ContextHolder相關(guān)屬性。事實(shí)上,在該方法內(nèi)其會(huì)將 requestAttributes與當(dāng)前線程進(jìn)行綁定。具體邏輯如下:

private void initContextHolders(HttpServletRequest request,
                                @Nullable LocaleContext localeContext, @Nullable RequestAttributes requestAttributes) {
    // 將參數(shù)localeContext設(shè)置為當(dāng)前線程的LocaleContext,并設(shè)置參數(shù)threadContextInheritable為true,表示上下文對(duì)象可以在子線程中繼承
    if (localeContext != null) {
        LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable);
    }
    // 將參數(shù)requestAttributes設(shè)置為當(dāng)前線程的RequestAttributes,并設(shè)置參數(shù)threadContextInheritable為true,表示上下文對(duì)象可以在子線程中繼承
    if (requestAttributes != null) {
        RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
    }
}

(Ps:RequestAttributesSpring框架中的一個(gè)接口,用于表示一個(gè)請(qǐng)求的屬性集合。它允許應(yīng)用程序在線程中存儲(chǔ)和訪問請(qǐng)求的屬性,這些屬性可以用于在請(qǐng)求的不同階段共享數(shù)據(jù)或傳遞數(shù)據(jù)。)

而在RequestContextHolder內(nèi)部的setRequestAttributes方法中,其會(huì)根據(jù)inheritable屬性的不同來將request屬性選擇性的放入requestAttributesHolderinheritableRequestAttributesHolder兩個(gè)不同的ThreadLocal

而兩者的區(qū)別在于子線程是否可以共享父線程屬性。而默認(rèn)情況下inheritable的取值為false,也就是說在SpringMVC默認(rèn)情況下requestAttributes是不會(huì)線程共享的。

(Ps:此處的RequestAttributes是我們之前提及ServletRequestAttributes的父接口)

進(jìn)一步,setRequestAttributes的內(nèi)部邏輯如下:

public static void setRequestAttributes(@Nullable RequestAttributes attributes, boolean inheritable) {
   if (attributes == null) {
      resetRequestAttributes();
   }
   else {
      if (inheritable) {
         inheritableRequestAttributesHolder.set(attributes);
         requestAttributesHolder.remove();
      }
      else {
         requestAttributesHolder.set(attributes);
         inheritableRequestAttributesHolder.remove();
      }
   }
}

總結(jié)

回到我們之前的問題,我們以多線程下無法從從請(qǐng)求頭中獲取相關(guān)屬性為入口,逐步深入剖析了其在多線程情況下無法失效的原因。具體來看,在SpringMVC中,如果我們想獲取當(dāng)前請(qǐng)求的Request對(duì)象,通常我們會(huì)通過RequestContextHolder進(jìn)行獲取。進(jìn)一步RequestContextHolder獲取Request對(duì)象需要先獲取ServletRequestAttributes對(duì)象,進(jìn)而通過其getRequest方法來獲取到當(dāng)前的請(qǐng)求信息。

換言之,如果想獲取當(dāng)前請(qǐng)求的Request對(duì)象,我們首先需要確保能獲取到ServletRequestAttributes這一中間信息,因?yàn)槠鋬?nèi)部會(huì)維護(hù)相關(guān)的請(qǐng)求對(duì)象。而在SpringMVC內(nèi)部ServletRequestAttributes在保存在RequestContextHolder中的ThreadLocal。

而默認(rèn)情況下,其實(shí)不支持父子線程間傳遞的,所以在多線程環(huán)境下當(dāng)我們通過RequestContextHolder獲取請(qǐng)求時(shí)會(huì)出現(xiàn)請(qǐng)求無法獲取的現(xiàn)象,而導(dǎo)致這一問題本質(zhì)發(fā)生的本質(zhì)原因在于ServletRequestAttributes并未實(shí)現(xiàn)父子線程間的共享!

以上就是SpringMVC多線程下無法獲取請(qǐng)求的原因和解決方法的詳細(xì)內(nèi)容,更多關(guān)于SpringMVC無法獲取請(qǐng)求的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • java中對(duì)象的強(qiáng)、軟、弱、虛四種引用詳解

    java中對(duì)象的強(qiáng)、軟、弱、虛四種引用詳解

    這篇文章主要介紹了java中對(duì)象的強(qiáng)、軟、弱、虛四種引用詳解,對(duì)象的引用分為4種,分別是強(qiáng)引用>軟引用>弱引用>虛引用,程序員可以通過不同的引用控制對(duì)象的生命周期,方便垃圾回收,使程序更加靈活的控制對(duì)象生命周期,需要的朋友可以參考下
    2023-09-09
  • Spring?Security過濾器鏈體系的實(shí)例詳解

    Spring?Security過濾器鏈體系的實(shí)例詳解

    這篇文章主要介紹了Spring?Security過濾器鏈體系,通過思維導(dǎo)圖可以很好的幫助大家理解配置類的相關(guān)知識(shí),結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2022-02-02
  • SpringBoot整合SpringSecurity認(rèn)證與授權(quán)

    SpringBoot整合SpringSecurity認(rèn)證與授權(quán)

    在項(xiàng)目開發(fā)中,權(quán)限認(rèn)證是很重要的,尤其是一些管理類的系統(tǒng),對(duì)于權(quán)限要求更為嚴(yán)格,本文主要介紹了SpringBoot整合SpringSecurity認(rèn)證與授權(quán),感興趣的可以了解一下
    2023-11-11
  • MyBatis中foreach標(biāo)簽的collection屬性的取值方式

    MyBatis中foreach標(biāo)簽的collection屬性的取值方式

    這篇文章主要介紹了MyBatis中foreach標(biāo)簽的collection屬性的取值方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • Maven中jar包沖突原理與解決辦法

    Maven中jar包沖突原理與解決辦法

    這篇文章主要介紹了Maven中jar包沖突原理與解決辦法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-09-09
  • java同步之如何寫一個(gè)鎖Lock

    java同步之如何寫一個(gè)鎖Lock

    本篇文章的目標(biāo)一是自己動(dòng)手寫一個(gè)鎖,這個(gè)鎖的功能很簡(jiǎn)單,能進(jìn)行正常的加鎖、解鎖操作。能更好地理解后面章節(jié)將要學(xué)習(xí)的AQS及各種同步器實(shí)現(xiàn)的原理。下面小編來和大家一起學(xué)習(xí)下吧
    2019-05-05
  • Java利用ZipOutputStream進(jìn)行高效壓縮的技巧詳解

    Java利用ZipOutputStream進(jìn)行高效壓縮的技巧詳解

    ZipOutputStream 是 Java 標(biāo)準(zhǔn)庫(kù)中用于 創(chuàng)建 ZIP 文件 的核心類,位于 java.util.zip 包中,本文主要和大家介紹了Java如何使用ZipOutputStream進(jìn)行高效壓縮,有需要的可以了解下
    2025-07-07
  • Java讓多線程按順序執(zhí)行的幾種方法

    Java讓多線程按順序執(zhí)行的幾種方法

    本文主要介紹了Java讓多線程按順序執(zhí)行的幾種方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-05-05
  • IDEA 當(dāng)前在線人數(shù)和歷史訪問量的示例代碼

    IDEA 當(dāng)前在線人數(shù)和歷史訪問量的示例代碼

    這篇文章主要介紹了IDEA 當(dāng)前在線人數(shù)和歷史訪問量的實(shí)例代碼,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-08-08
  • SpringCloud解決Feign異步回調(diào)問題(SpringBoot+Async+Future實(shí)現(xiàn))

    SpringCloud解決Feign異步回調(diào)問題(SpringBoot+Async+Future實(shí)現(xiàn))

    這篇文章主要介紹了SpringCloud解決Feign異步回調(diào)問題(SpringBoot+Async+Future實(shí)現(xiàn)),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-11-11

最新評(píng)論

仁化县| 南靖县| 潞西市| 东乡族自治县| 怀安县| 固阳县| 资溪县| 额敏县| 湘乡市| 湘乡市| 马关县| 吕梁市| 简阳市| 门源| 普兰县| 双辽市| 卢龙县| 鹿邑县| 潞西市| 禄丰县| 元阳县| 瓮安县| 西宁市| 隆昌县| 水城县| 紫金县| 登封市| 永仁县| 沂南县| 安徽省| 阳泉市| 镇坪县| 庆阳市| 定日县| 吴川市| 玉龙| 迁西县| 怀集县| 闽清县| 托里县| 漳平市|