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

SpringMVC在多線程下請(qǐng)求頭獲取失敗問(wèn)題的解決方案

 更新時(shí)間:2024年08月15日 08:47:49   作者:毅航  
這篇文章主要介紹了我們就對(duì)多線程環(huán)境下使用SpringMVC中RequestContextHolder無(wú)法獲取請(qǐng)求的問(wèn)題進(jìn)行了深入的分析,并針對(duì)相關(guān)問(wèn)題給出了相應(yīng)的解決方案,需要的朋友可以參考下

前言

在日常的SpringMVC開發(fā)中,我們通常會(huì)在請(qǐng)求頭中自定義一些參數(shù)信息,之后借助SpringMVC提供的RequestContextHolder來(lái)完成當(dāng)前請(qǐng)求的獲取,此時(shí)代碼邏輯大致如下:

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;
}

上述代碼中,我們首先通過(guò)RequestContextHolder提供的getRequestAttributes方法獲取到一個(gè) ServletRequestAttributes 對(duì)象。而ServletRequestAttributesSpring MVC中主要用于訪問(wèn)和管理與當(dāng)前HTTP請(qǐng)求相關(guān)的屬性, 并且提供了對(duì)HttpServletRequestHttpServletResponse對(duì)象的訪問(wèn)的API

進(jìn)一步,當(dāng)獲取到ServletRequestAttributes對(duì)象后,我們就可以通過(guò)其提供的getRequest來(lái)獲取到當(dāng)前請(qǐng)求的Reqeust對(duì)象。而當(dāng)獲取到當(dāng)請(qǐng)求的Reqeust對(duì)象后,我們即可讀取請(qǐng)求頭,從而獲取到請(qǐng)求頭中自定義的key-value鍵值對(duì)。

請(qǐng)求頭丟失的問(wèn)題

如果是在單線程情況下,上述邏輯不存在任何問(wèn)題。但如果是多線程環(huán)境下,你會(huì)發(fā)現(xiàn)程序會(huì)莫名其妙出現(xiàn)空指針異常。此時(shí)出現(xiàn)的問(wèn)題具體如下:

Controller測(cè)試接口

@RestController
@RequestMapping("/test")
@Slf4j
public class TestController {
    @GetMapping("/missing-request-header")
    public String getMissingRequestHeader() {
        // 主線程獲取請(qǐng)求頭信息
        String mainThreadLanguages = ServletUtils.getLanguagesExistProblem();
        log.info("主線程獲取請(qǐng)求頭信息:{}", mainThreadLanguages);
        new Thread(() -> {
            // 子線程獲取請(qǐng)求頭信息
            String subThreadLanguages = ServletUtils.getLanguagesExistProblem();
            log.info("子線程獲取請(qǐng)求頭信息:{}", subThreadLanguages);
        }).start();
        return "success";
    }
}

ServletUtils.getLanguagesExistProblem()具體邏輯

@Slf4j
public class ServletUtils {

    private final static String X_CLIENT_LANG = "X-CLIENT-LANG";

    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";
    }
}

在上述代碼中,我們?cè)?code>TestController中啟用了一個(gè)新的線程,嘗試去通過(guò)getLanguagesExistProblem讀取請(qǐng)求頭中我們自定義的"X-CLIENT-LANG頭信息。然而,當(dāng)運(yùn)行代碼后你會(huì)發(fā)現(xiàn)出現(xiàn)代碼無(wú)法通過(guò)Assert.notNull(request);這個(gè)斷言信息。即當(dāng)子線程嘗試去讀取請(qǐng)求中的"X-CLIENT-LANG信息時(shí),其在子線程中無(wú)法獲取到當(dāng)前請(qǐng)求中的Request對(duì)象,從而出現(xiàn)了空指針的異常。

而這恰恰也是我們開發(fā)中常見(jiàn)的在多線程環(huán)境下請(qǐng)求頭丟失的問(wèn)題。簡(jiǎn)單來(lái)看,對(duì)于SpringMVC而言,每個(gè)請(qǐng)求request信息是存儲(chǔ)在ThreadLocal中,而對(duì)于ThreadLocal而言,其key為當(dāng)前線程,因此每個(gè)線程一個(gè)存儲(chǔ)份Request對(duì)象,因此Request對(duì)象只與當(dāng)前線程關(guān)聯(lián)。如果,我們嘗試在當(dāng)前線程中,再啟動(dòng)一個(gè)子線程去獲取Reqeust其必然是無(wú)法獲取到主線程的Request對(duì)象。

進(jìn)一步,針對(duì)多線程環(huán)境下無(wú)法獲取請(qǐng)求的這一問(wèn)題,筆者在此提供兩個(gè)解決思路。希望對(duì)你能有所啟發(fā)。

解決方案

在這里我們先對(duì)網(wǎng)上一種錯(cuò)誤的方案進(jìn)行糾正。對(duì)于多線程環(huán)境下無(wú)法獲取請(qǐng)求頭的這一問(wèn)題,網(wǎng)上其實(shí)很早就有人給出了解決方案,其大致思路是調(diào)用RequestContextHoldersetRequestAttributesinheritable屬性置為true,從而實(shí)現(xiàn)父子線程對(duì)于Request對(duì)象的共享。之所以這么做的原因在于SpringMVC中有如下的代碼:

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();
      }
   }
}

SpringMVC內(nèi)對(duì),對(duì)于RequestContextHolder而言,當(dāng)我們指定其requestAttributestrue時(shí),其會(huì)將相關(guān)的請(qǐng)求信息放入到InheritableThreadLocal中。而InheritableThreadLocalThreadLocal 的子類,其可以實(shí)現(xiàn)父線程和子線程之間數(shù)據(jù)的共享。因此當(dāng)使用 InheritableThreadLocal 保存數(shù)據(jù)時(shí),子線程在創(chuàng)建時(shí)會(huì)繼承父線程中的 ThreadLocal 變量值。通過(guò)這樣的方式從而實(shí)現(xiàn)多線程環(huán)境下請(qǐng)求的獲取。

但這樣做的前提在于其必須確保子線程一定在父線程后執(zhí)行完畢,而如果子線程執(zhí)行慢,父線程執(zhí)行較快,已經(jīng)會(huì)存在子線程中數(shù)據(jù)獲取的問(wèn)題!這么說(shuō)可能比較晦澀,接下來(lái)我們不妨通過(guò)一個(gè)簡(jiǎn)單的例子來(lái)分析這一方法存在的問(wèn)題

@GetMapping("/get-request-header-in-thread")
    public String getRequestHeaderInThread() {
        // 主線程獲取請(qǐng)求頭信息
        String mainThreadLanguages = ServletUtils.getLanguages();
        log.info("主線程獲取請(qǐng)求頭信息:{}", mainThreadLanguages);
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            // 子線程獲取請(qǐng)求頭信息
            String subThreadLanguages = ServletUtils.getLanguages();
            log.info("子線程獲取請(qǐng)求頭信息:{}", subThreadLanguages);
        }).start();
        return "success";
    }

(注:此處的ServletUtils.getLanguages()邏輯可參考之前代碼)

在上述代碼中,我們?cè)?code>getRequestHeaderInThread方法中重新一個(gè)子線程去嘗試獲取請(qǐng)求中的語(yǔ)言信息。而我們的請(qǐng)求如下:

在請(qǐng)求頭中,我們?cè)O(shè)定的本次請(qǐng)求的語(yǔ)言頭為X-CLIENT-LANGen,當(dāng)請(qǐng)求get-request-header-in-thread這一路徑后,執(zhí)行結(jié)果如下:

可以看到,兩行日志打印時(shí)間間隔相差5秒中,而這5秒恰好正是我們代碼中Sleep的時(shí)間。進(jìn)一步,子線程打印出的內(nèi)容zh-en。即在子線程中其在獲取請(qǐng)求頭時(shí),本質(zhì)是獲取到了我們?cè)?code>getLanguages定義的默認(rèn)內(nèi)容,而非我們請(qǐng)求頭中X-CLIENT-LANG對(duì)應(yīng)的en。換言之,網(wǎng)上流傳的將RequestContextHolder而言,當(dāng)我們指定其requestAttributestrue能有效解決多線程下SpringMVC中獲取請(qǐng)求的方案完全是有問(wèn)題的。那如何能解決這一問(wèn)題呢?其實(shí)也很簡(jiǎn)單,如果能確保只開啟有限線程的話,完全可以借助CountDownLatch來(lái)實(shí)現(xiàn)多線程間的協(xié)調(diào)工作。改造后的代碼如下:

@GetMapping("/get-request-header-in-thread")
public String getRequestHeaderInThread() {
    // 主線程獲取請(qǐng)求頭信息
    String mainThreadLanguages = ServletUtils.getLanguages();
    CountDownLatch latch = new CountDownLatch(1);
    log.info("主線程獲取請(qǐng)求頭信息:{}", mainThreadLanguages);
    new Thread(() -> {
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        // 子線程獲取請(qǐng)求頭信息
        String subThreadLanguages = ServletUtils.getLanguages();
        log.info("子線程獲取請(qǐng)求頭信息:{}", subThreadLanguages);
        latch.countDown();
    }).start();
    // 等待計(jì)數(shù)器變?yōu)榱?
    try {
        latch.await();
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    log.info("確保父子線程全部執(zhí)行完畢");
    return "success";
}

但在開發(fā)中,如果遇到子線程比較耗時(shí)的操作,上述代碼的性能又成為了效率的瓶頸。這與我們使用多線程開發(fā)的初衷相悖。事實(shí)上上,除了上述的方案外,我們還可以采用緩存當(dāng)前Request的操作來(lái)實(shí)現(xiàn)請(qǐng)求的共享。其具體邏輯如下:

@GetMapping("/get-request-header-in-async-thread/{isJoin}")
    public String getRequestHeaderInThread() {
        // 主線程獲取請(qǐng)求頭信息
        String mainThreadLanguages = ServletUtils.getLanguages();
        log.info("主線程獲取請(qǐng)求頭信息:{}", mainThreadLanguages);
        // 獲取當(dāng)前servletRequestAttributes對(duì)象
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        new Thread(() -> {
        // 將servletRequestAttributes設(shè)定到子線程中
        RequestContextHolder.setRequestAttributes(servletRequestAttributes);
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            // 子線程獲取請(qǐng)求頭信息
            String subThreadLanguages = ServletUtils.getLanguages();
            log.info("子線程獲取請(qǐng)求頭信息:{}", subThreadLanguages);
        }).start();
        return "success";
    }

在上述代碼中,我們手動(dòng)獲取到當(dāng)前線程servletRequestAttributes對(duì)象,然后將子線程代碼執(zhí)行前, 手動(dòng)給主線程中的ServletRequestAttributes設(shè)置到子線程中,從而是確保實(shí)現(xiàn)子線程也能獲取到相關(guān)的請(qǐng)求對(duì)象。

總結(jié)

至此,我們就對(duì)多線程環(huán)境下使用SpringMVCRequestContextHolder無(wú)法獲取請(qǐng)求的問(wèn)題進(jìn)行了深入的分析,并針對(duì)相關(guān)問(wèn)題給出了相應(yīng)的解決方案。具體來(lái)看,造成多線程環(huán)境下請(qǐng)求無(wú)法獲取的原因在于在默認(rèn)情況下SpringMVC內(nèi)部對(duì)于請(qǐng)求頭的存放于在ThnreadLocal。而如果手動(dòng)對(duì)RequestContextHolder中的inheritable設(shè)定為True,其會(huì)將請(qǐng)求頭存放于InheritableThreadLocal,從而實(shí)現(xiàn)父子線程請(qǐng)求頭的共享。

但當(dāng)請(qǐng)求頭存放于InheritableThreadLocal時(shí),如果父線程先銷毀,則子線程依舊存在無(wú)法獲取請(qǐng)求頭的問(wèn)題。 針對(duì)這一問(wèn)題,我們給出了線程同步的解決方案。同時(shí),還給出了更加通用的方案以徹底解決多線程環(huán)境下請(qǐng)求頭丟失的問(wèn)題。

以上就是SpringMVC在多線程下請(qǐng)求頭獲取失敗問(wèn)題的解決方案的詳細(xì)內(nèi)容,更多關(guān)于SpringMVC請(qǐng)求頭獲取失敗的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Java實(shí)現(xiàn)添加、驗(yàn)證PDF數(shù)字簽名的方法示例

    Java實(shí)現(xiàn)添加、驗(yàn)證PDF數(shù)字簽名的方法示例

    在設(shè)置文檔內(nèi)容保護(hù)的方法中,除了對(duì)文檔加密、添加水印外,應(yīng)用數(shù)字簽名也是一種有效防偽手段。本文就使用Java實(shí)現(xiàn)添加、驗(yàn)證PDF數(shù)字簽名,感興趣的可以了解一下
    2021-07-07
  • 通過(guò)實(shí)例解析spring環(huán)繞通知原理及用法

    通過(guò)實(shí)例解析spring環(huán)繞通知原理及用法

    這篇文章主要介紹了通過(guò)實(shí)例解析spring環(huán)繞通知原理及用法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-10-10
  • SpringBoot基于knife4j生成接口文檔方式

    SpringBoot基于knife4j生成接口文檔方式

    Knife4j介紹一個(gè)基于Swagger的JavaAPI文檔生成工具,提供美觀界面和高級(jí)功能,本文詳細(xì)介紹了在SpringBoot項(xiàng)目中集成Knife4j的方法,包括導(dǎo)入依賴、創(chuàng)建Swagger配置和WebMvcConfiguration配置類等
    2026-05-05
  • Java打印高質(zhì)量日志的10條方法詳解

    Java打印高質(zhì)量日志的10條方法詳解

    你以為打日志是小事,也許正是這種輕視,讓你在凌晨三點(diǎn)被生產(chǎn)事故電話吵醒,一個(gè)優(yōu)秀的工程師和普通碼農(nóng)的區(qū)別,往往體現(xiàn)在那些看似微不足道的細(xì)節(jié)上,下面我們就來(lái)看看如何打印高質(zhì)量日志吧
    2025-06-06
  • Nacos多個(gè)實(shí)例的服務(wù)調(diào)用失敗問(wèn)題及解決

    Nacos多個(gè)實(shí)例的服務(wù)調(diào)用失敗問(wèn)題及解決

    在微服務(wù)開發(fā)中,Nacos上出現(xiàn)多個(gè)同一服務(wù)實(shí)例導(dǎo)致OpenFeign調(diào)用失敗,因?yàn)镺penFeign使用SpringCloudLoadbalancer,而SpringCloudLoadbalancer的底層默認(rèn)負(fù)載均衡策略不支持Nacos,通過(guò)將負(fù)載均衡策略設(shè)置為Nacos,可以解決調(diào)用失敗的問(wèn)題
    2026-02-02
  • SpringBoot整合MinIO實(shí)現(xiàn)文件存儲(chǔ)系統(tǒng)的代碼示例

    SpringBoot整合MinIO實(shí)現(xiàn)文件存儲(chǔ)系統(tǒng)的代碼示例

    在現(xiàn)代的應(yīng)用程序中,文件存儲(chǔ)和管理是一個(gè)常見(jiàn)的需求,MinIO是一個(gè)開源的對(duì)象存儲(chǔ)系統(tǒng),與Spring?Boot框架結(jié)合使用,可以快速構(gòu)建高性能的文件存儲(chǔ)系統(tǒng),本文將介紹如何使用Spring?Boot和MinIO來(lái)實(shí)現(xiàn)文件存儲(chǔ)系統(tǒng)
    2023-06-06
  • springboot集成mybatisplus的詳細(xì)步驟

    springboot集成mybatisplus的詳細(xì)步驟

    MyBatis-Plus (opens new window)(簡(jiǎn)稱 MP)是一個(gè) MyBatis (opens new window)的增強(qiáng)工具,在 MyBatis 的基礎(chǔ)上只做增強(qiáng)不做改變,為簡(jiǎn)化開發(fā)、提高效率而生,這篇文章主要介紹了springboot四步集成mybatisplus,需要的朋友可以參考下
    2022-10-10
  • 詳解如何在Java中重寫equals()和hashCode()方法

    詳解如何在Java中重寫equals()和hashCode()方法

    在 Java 中,equals() 和 hashCode() 方法是 Object 類中定義的重要方法,它們用于比較對(duì)象的相等性以及計(jì)算對(duì)象的哈希值,本文將詳細(xì)介紹如何在 Java 中重寫 equals() 和 hashCode() 方法,并討論其最佳實(shí)踐,需要的朋友可以參考下
    2024-08-08
  • mybatis中resulthandler的用法

    mybatis中resulthandler的用法

    這篇文章主要介紹了mybatis中resulthandler的用法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • MyBatis-Plus?中?typeHandler?的使用實(shí)例詳解

    MyBatis-Plus?中?typeHandler?的使用實(shí)例詳解

    本文介紹了在MyBatis-Plus中如何使用typeHandler處理json格式字段和自定義typeHandler,通過(guò)使用JacksonTypeHandler,可以簡(jiǎn)單實(shí)現(xiàn)將實(shí)體類字段轉(zhuǎn)換為json格式存儲(chǔ),感興趣的朋友跟隨小編一起看看吧
    2024-10-10

最新評(píng)論

平安县| 怀集县| 桂东县| 剑川县| 工布江达县| 务川| 惠水县| 岳池县| 修水县| 侯马市| 江川县| 临桂县| 苗栗市| 宜城市| 伽师县| 静乐县| 顺昌县| 渝中区| 巨野县| 康定县| 麟游县| 浦东新区| 盐亭县| 黄大仙区| 兖州市| 临城县| 海安县| 奉化市| 竹山县| 衡东县| 门头沟区| 敦化市| 清新县| 琼结县| 土默特右旗| 曲松县| 宝鸡市| 渭南市| 万州区| 宁津县| 射阳县|