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

springboot?正確的在異步線程中使用request的示例代碼

 更新時(shí)間:2022年07月15日 08:46:46   作者:mysgk  
這篇文章主要介紹了springboot中如何正確的在異步線程中使用request,本文通過示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

起因:

有后端同事反饋在異步線程中獲取了request中的參數(shù),然后下一個(gè)請求是get請求的話,發(fā)現(xiàn)會偶爾出現(xiàn)參數(shù)丟失的問題.

示例代碼:

    @GetMapping("/getParams")
    public String getParams(String a, int b) {
        return "get success";
    }
    @PostMapping("/postTest")
    public String postTest(HttpServletRequest request,String age, String name) {
        
        new Thread(new Runnable() {
            @Override
            public void run() {
                String age2 = request.getParameter("age");
                String name2 = request.getParameter("name");
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
               String age3 = request.getParameter("age");
               String name3 = request.getParameter("name");
               System.out.println("age1: " + age + " , name1: " + name + " , age2: " + age2 + " , name2: " + name2 + " , age3: " + age3 + " , name3: " + name3);
            }
        }).start();
        return "post success";
    }

異常信息如下

java.lang.IllegalStateException: 
  Optional int parameter 'b' is present but cannot be translated into a null value due to being declared as a primitive type. 
  Consider declaring it as object wrapper for the corresponding primitive type

看到這里大家可以猜一下是為什么.

我的第一反應(yīng)是不可能,肯定是前端同學(xué)寫的代碼有問題,這么簡單的一個(gè)接口怎么可能有問題,然而等同事復(fù)現(xiàn)后就只能默默debug了.

大概追了一下源碼,發(fā)現(xiàn)

spring 在做參數(shù)解析的時(shí)候沒有獲取到參數(shù),方法如下:

org.springframework.web.method.annotation.RequestParamMethodArgumentResolver#resolveName

而且很奇怪,queryString 不是null ,獲取到了正確的參數(shù), 但是 parameterMap 卻是空的.

正常來說 parameterMap 里面應(yīng)該存放有 queryString 解析后的參數(shù).

如圖:

發(fā)現(xiàn)有人踩過坑,但是沒解決

搜索了一下,發(fā)現(xiàn)有人碰到過類似的情況

偶現(xiàn)的MissingServletRequestParameterException,誰動(dòng)了我的參數(shù)?

由于Tomcat中,Request以及Response對象都是會被循環(huán)使用的,因此這個(gè)時(shí)候也是整個(gè)Request被重置的時(shí)候。

所以根本原因是,在Parameter被重置了之后,didQueryParameters又被置成了true,導(dǎo)致新的請求參數(shù)沒有被正確解析,就報(bào)錯(cuò)了(此時(shí)的parameterMap已經(jīng)被重置,為空)。

而didQueryParameters只有在一種情況下才會被置為true,也就是handleQueryParameters方法被調(diào)用時(shí)。

而handleQueryParameters會在多個(gè)場景中被調(diào)用,其中一個(gè)就是getParameterValues,獲取請求參數(shù)的值。

大概就是說 tomcat 會復(fù)用Request對象,在異步中使用request中的參數(shù)可能會影響下一次 請求的參數(shù)解析過程.

最后文章作者的結(jié)論就是

不要將HttpServletRequest傳遞到任何異步方法中!

嘗試尋找官方支持

看到這里我還是有點(diǎn)不信,心想tomcat不會這么拉吧,異步都不支持,不可能吧...

于是我就去 tomcat的 bugzilla 搜了一下,居然沒搜索到相關(guān)的問題.

然后我還是有點(diǎn)不甘心,tomcat 沒有 ,spring框架出來這么久難道就沒人碰到過這種問題提出疑問嗎?

又去 spring的 issue 里面去搜,可能是我的關(guān)鍵詞沒搜對,還是沒找到什么有用信息.

這時(shí)我就有點(diǎn)泄氣了,官方都沒解決這個(gè)問題我咋個(gè)辦?

嘗試自己解決

不過我又突然想到既然參數(shù)解析的時(shí)候 queryString 里面有參數(shù),那豈不是自己再解析一次不就完美了嗎?

那這個(gè)時(shí)候我們只要

  1. 繼承原始的參數(shù)解析器,當(dāng)它獲取不到的時(shí)候嘗試從 queryString 尋找,queryString 中存在我們就返回 queryString 中的參數(shù).
  2. 替換掉原始的參數(shù)解析器,具體做法就是 在 RequestMappingHandlerAdapter 初始化后,拿到 argumentResolvers,遍歷所有的參數(shù)解析器,找到 RequestParamMethodArgumentResolver ,換成我們的即可.

這里有兩個(gè)問題需要注意就是 :

  1. argumentResolvers 是一個(gè) UnmodifiableList,不能直接set
  2. RequestParamMethodArgumentResolver 有兩個(gè),其中一個(gè) useDefaultResolution 屬性值為 true,另外一個(gè) 屬性值為 false,

解析get請求 url中參數(shù)的是 useDefaultResolution 屬性值為 true 的那一個(gè).
spring源碼對應(yīng)位置:

org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#getDefaultInitBinderArgumentResolvers

private List<HandlerMethodArgumentResolver> getDefaultInitBinderArgumentResolvers() {
	List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(20);

	// Annotation-based argument resolution
	resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
	resolvers.add(new RequestParamMapMethodArgumentResolver());
	resolvers.add(new PathVariableMethodArgumentResolver());
	resolvers.add(new PathVariableMapMethodArgumentResolver());
	resolvers.add(new MatrixVariableMethodArgumentResolver());
	resolvers.add(new MatrixVariableMapMethodArgumentResolver());
	resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
	resolvers.add(new SessionAttributeMethodArgumentResolver());
	resolvers.add(new RequestAttributeMethodArgumentResolver());

	// Type-based argument resolution
	resolvers.add(new ServletRequestMethodArgumentResolver());
	resolvers.add(new ServletResponseMethodArgumentResolver());

	// Custom arguments
	if (getCustomArgumentResolvers() != null) {
		resolvers.addAll(getCustomArgumentResolvers());
	}

	// Catch-all
	resolvers.add(new PrincipalMethodArgumentResolver());
	resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));

	return resolvers;
}

這個(gè)方案實(shí)現(xiàn)以后給項(xiàng)目組上的同事集成后看起來是沒什么問題了.

參數(shù)也能獲取到了,業(yè)務(wù)也跑通了,也不會報(bào)錯(cuò)了.

但是其實(shí)這是一個(gè)治標(biāo)不治本的方案
還存在一些問題:

  • 只能解決接口參數(shù)綁定的問題,不能解決后續(xù)從request中獲取參數(shù)的問題.
  • 通過壓測, postTest 和 getParams 這兩個(gè)接口, 發(fā)現(xiàn) age3/name3 大概會出現(xiàn)null, age2/name2 也可能獲取到null, 只有接口參數(shù)中的 name 和age 能正確獲取到.

還是甩給官方

這個(gè)時(shí)候我已經(jīng)沒什么好的辦法了,于是給spring 提了一個(gè)issue:

in asynchronous tasks use request.getParameter(), It may cause the next "get request" to fail to obtain parameters

等待回復(fù)是痛苦的,issue提了以后

等了三天,開發(fā)者叫我提交一個(gè)復(fù)現(xiàn)的 demo (大家也可以嘗試復(fù)現(xiàn)一下).

又等了兩天,我想著這樣等也不是個(gè)辦法

主要是我看到 issue 還有 1.2k,輪到我的時(shí)候估計(jì)都猴年馬月了

而且就算修復(fù)了估計(jì)也是新版本, 在項(xiàng)目上升級 springboot 版本 估計(jì)也不太現(xiàn)實(shí)(版本不兼容)

解決

于是我開始看源碼.直到我看到了一個(gè)

org.apache.coyote.Request#setHook

它里面有個(gè) ActionCode,是一個(gè)枚舉類型,其中有一個(gè)枚舉值是

ASYNC_START

這玩意看著就和異步有關(guān).于是開始搜索相關(guān)資料

最后終于在

RequestLoggingFilter: afterRequest is executed before Async servlet finishes

中找到答案.

結(jié)合我的代碼改造如下

@PostMapping("/postTest")
    public String postTest(HttpServletRequest request, HttpServletResponse response, String age, String name) {
        AsyncContext asyncContext =
                request.isAsyncStarted()
                        ? request.getAsyncContext()
                        : request.startAsync(request, response);
        asyncContext.start(new Runnable() {
            @Override
            public void run() {
                String age2 = request.getParameter("age");
                String name2 = request.getParameter("name");
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                String age3 = request.getParameter("age");
                String name3 = request.getParameter("name");
                System.out.println("age1: " + age + " , name1: " + name + " , age2: " + age2 + " , name2: " + name2 + " , age3: " + age3 + " , name3: " + name3);
                asyncContext.complete();
            }
        });

        return "post success";
    }

ps: 此處應(yīng)該用線程池提交任務(wù),不想改了
壓測一把發(fā)現(xiàn)沒啥問題

結(jié)論

springboot 中如何正確的在異步線程中使用request

  • 使用異步前先獲取 AsyncContext
  • 使用線程池處理任務(wù)
  • 任務(wù)完成后調(diào)用asyncContext.complete()

到此這篇關(guān)于springboot 中如何正確的在異步線程中使用request的文章就介紹到這了,更多相關(guān)springboot 異步線程使用request內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • java微信公眾號企業(yè)付款開發(fā)

    java微信公眾號企業(yè)付款開發(fā)

    這篇文章主要為大家詳細(xì)介紹了java微信公眾號企業(yè)付款開發(fā),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-09-09
  • 使用Spring?Boot的原因解析

    使用Spring?Boot的原因解析

    Spring Boot是Spring家族的成員,它是一個(gè)全新的框架,它的設(shè)計(jì)目的是盡可能簡單和快速的開發(fā)、運(yùn)行Spring應(yīng)用程序,簡化配置,這篇文章主要介紹了為什么使用Spring?Boot,需要的朋友可以參考下
    2022-07-07
  • Spring?Cloud?中使用?Sentinel?實(shí)現(xiàn)服務(wù)限流的兩種方式

    Spring?Cloud?中使用?Sentinel?實(shí)現(xiàn)服務(wù)限流的兩種方式

    這篇文章主要介紹了Spring?Cloud?中使用?Sentinel?實(shí)現(xiàn)服務(wù)限流的方式,通過示例代碼主要介紹了Sentinel的兩種實(shí)現(xiàn)限流的方式,需要的朋友可以參考下
    2024-03-03
  • Java 重試框架 Sisyphus 配置的兩種方式

    Java 重試框架 Sisyphus 配置的兩種方式

    這一節(jié)讓我們一起學(xué)習(xí)下 sisyphus 基于函數(shù)式的配置和注解式的配置。為了滿足更加方便的配置,Retryer 類提供了許多可以配置的信息。下面一起進(jìn)入文章了解詳情內(nèi)容
    2021-11-11
  • Idea如何導(dǎo)入java mysql驅(qū)動(dòng)包

    Idea如何導(dǎo)入java mysql驅(qū)動(dòng)包

    本文介紹了如何在IntelliJ IDEA中配置MySQL數(shù)據(jù)庫連接,首先下載MySQL Connector/J驅(qū)動(dòng)并解壓,然后在Idea項(xiàng)目中創(chuàng)建lib文件夾并將.jar文件復(fù)制到該文件夾,接著,將.jar文件添加為項(xiàng)目庫,通過這些步驟,可以成功配置MySQL數(shù)據(jù)庫連接
    2024-12-12
  • Java反射的兩種使用方法(推薦)

    Java反射的兩種使用方法(推薦)

    這篇文章主要介紹了Java反射的兩種使用方法,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2018-05-05
  • Java文本文件操作方法實(shí)例詳解

    Java文本文件操作方法實(shí)例詳解

    這篇文章主要介紹了Java文本文件操作方法,以實(shí)例形式較為詳細(xì)的分析了java操作文本文件的相關(guān)技巧,需要的朋友可以參考下
    2015-06-06
  • 一文解決System.in關(guān)閉后無法再繼續(xù)使用流的問題

    一文解決System.in關(guān)閉后無法再繼續(xù)使用流的問題

    這篇文章主要給大家介紹如何解決System.in關(guān)閉后無法再繼續(xù)使用流的問題,文中有詳細(xì)的解決方法和代碼示例,具有一定的參考價(jià)值,需要的朋友可以參考下
    2023-07-07
  • springboot 基于Tomcat容器的自啟動(dòng)流程分析

    springboot 基于Tomcat容器的自啟動(dòng)流程分析

    這篇文章主要介紹了springboot 基于Tomcat容器的自啟動(dòng)流程分析,Spring通過注解導(dǎo)入Bean大體可分為四種方式,我們主要來說Import的兩種實(shí)現(xiàn)方法,需要的朋友可以參考下
    2020-02-02
  • 配置pom.xml用maven打包java工程的方法(推薦)

    配置pom.xml用maven打包java工程的方法(推薦)

    下面小編就為大家?guī)硪黄渲胮om.xml用maven打包java工程的方法(推薦)。小編覺得挺不錯(cuò)的, 現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2016-06-06

最新評論

海伦市| 弥渡县| 德令哈市| 石棉县| 彭水| 奈曼旗| 交城县| 鄂尔多斯市| 盐山县| 麻阳| 米林县| 苗栗县| 德昌县| 牟定县| 万源市| 襄樊市| 阜南县| 青龙| 工布江达县| 原平市| 日土县| 保靖县| 锡林郭勒盟| 文安县| 大足县| 阿鲁科尔沁旗| 鸡泽县| 汉寿县| 漾濞| 屏东市| 镇江市| 察隅县| 二连浩特市| 宾川县| 怀仁县| 厦门市| 万山特区| 金寨县| 南岸区| 石河子市| 铜鼓县|