HttpServletRequest在Spring中的獲取和注入

問題

在最近一次團隊review代碼時合瓢,團隊成員發(fā)現(xiàn)有將HttpServletRequest 直接通過@Autowired注入的情況退个,于是大家產(chǎn)生了一個疑問,HttpServletRequest并非Spring中的類勋功,且在沒有手動通過@Bean的方式注入递鹉,Spring是怎么做到幫開發(fā)者完成注入的?

同時,我們知道ioc容器中默認注入的Bean是單例他匪,而每個請求都是獨立的菇存,這樣不會出問題嗎?

為了研究明白為什么邦蜜,我找了些資料依鸥,同時寫了個簡單的demo研究了下。

Demo類如下:

/**
 * @author zhangrenwei
 * @date 2022/8/1
 * @description replace this
 **/
@RestController
public class HttpServletRequestTest {

    @Autowired
    private HttpServletRequest httpServletRequest;

    @GetMapping("/sayHi")
    public void sayHi(String name) {
        System.out.println("hello: " +  name);
    }

    @PostConstruct
    public void after(){
        System.out.println(this.httpServletRequest);
    }

}

疑慮1

作為一個外部對象悼沈,HttpServletRequest為什么可以在Spring項目中通過注入的方式獲燃佟?

The PostConstruct annotation is used on a method that needs to be executed after dependency injection is done to perform any initialization. This method MUST be invoked before the class is put into service. This annotation MUST be supported on all classes that support dependency injection.

由關(guān)于@PostConstruct的描述知絮供,@PostConstruct注解被用在執(zhí)行完依賴注入之后的方法調(diào)用上衣吠,我們將斷點打在上述demo的第19行,即可查看HttpServletRequest httpServletRequest實例化之后的情況壤靶。

image.png

由上圖我們可以看到這個httpServletRequest對象是一個代理對象(org.springframework.web.context.support.WebApplicationContextUtils.RequestObjectFactory)缚俏,該對象是一個請求的對象工廠。進入WebApplicationContextUtils這個類贮乳,Command + F12我們發(fā)現(xiàn)了一個往 ConfigurableListableBeanFactory工廠對象注入bean對象的方法registerWebApplicationScopes忧换。

該方法有這樣一行代碼,beanFactory.``registerResolvableDependency(ServletRequest.class, new RequestObjectFactory()``);

由于HttpServletRequest正是繼承自ServletRequest向拆,這里引起了我們的關(guān)注亚茬。

image.png

ConfigurableListableBeanFactoryregisterResolvableDependency方法又是用什么的呢?我們繼續(xù)往下看亲铡。

image.png

使用相應(yīng)的自動裝配值注冊一個特殊的依賴類型才写。

這適用于被認為可自動裝配但未在工廠中定義為 bean 的工廠/上下文引用

看到這里我們就明白了,原來Spring可以為一個類注入另一個完全不同的對象值奖蔓,這樣從ioc容器中引用這個類的時候其實拿到的就是這個對象值赞草。且上面的描述正好回應(yīng)了,為什么我們沒有手動定義HttpServletRequest卻可以完成它的自動裝配吆鹤,秘訣就在這里厨疙。(進一步拓展思考,上面兩張圖一起看疑务,除了ServletRequest.class沾凄,ServletResponse.class,HttpSession.class以及WebRequest.class類型對象知允,均被Spring自動注入撒蟀,可以直接通過注解的方式引用)。

繼續(xù)進入registerResolvableDependency方法温鸽,我們發(fā)現(xiàn)該方法的實現(xiàn)是將ServletRequest.class的依賴類型作為key保屯, RequestObjectFactory作為裝配的value手负,放入了resolvableDependencies這個map中。

image.png

疑慮2

HttpServletRequest注入和引用我們知道了姑尺,那么針對每個獨立的請求竟终,多線程場景下,通過自動注入的方式切蟋,HttpServletRequest 是否會有線程安全的問題呢统捶?

我們已經(jīng)知道注入拿到的HttpServletRequest返回的對象其實是下面這個

org.springframework.web.context.support.WebApplicationContextUtils.``RequestObjectFactory對象,那么它是怎么做到線程安全的呢柄粹?

我們看它的具體實現(xiàn)喘鸟。

image.png

沿著currentRequestAttributes()方法一路點擊,最終發(fā)現(xiàn)它的返回值镰惦,是來自org.springframework.web.context.request.RequestContextHolder#requestAttributesHolder迷守。

image.png

requestAttributesHolder對象是一個ThreadLocal對象,由此我們明白了旺入,Spring自動注入的HttpServletRequest能夠保證請求的唯一性兑凿,原來是通過跟每個線程綁定了,從ThreadLocal中取得請求信息茵瘾,由此保證的線程安全礼华!

我們再來拓展一下,既然請求信息是從ThreadLocal中取的拗秘,那么請求信息是如何放進去的呢圣絮?

我們找到requestAttributesHolder屬性的set方法,在這里打一個斷點雕旨,重啟系統(tǒng)扮匠,并發(fā)起一個http請求。

image.png

最終凡涩,通過調(diào)用堆棧信息棒搜,我們看到了org.springframework.web.filter.RequestContextFilter#doFilterInternal

中調(diào)用了initContextHolder方法,而initContextHolder方法調(diào)用了requestAttributesHolder這個ThreadLocal的set方法活箕。

每個http請求過來會進入RequestContextFilter這個filter力麸,在這個filter中會將request信息設(shè)置到當(dāng)前的線程里。到這里育韩,所有的謎團都解開了克蚂。

總結(jié)

我們來做一個總結(jié),在代碼中通過注解注入HttpServletRequest的方式筋讨,拿到的其實并不是真正的 HttpServletRequest埃叭,而是一個Spring項目啟動時自動注入的代理對象org.springframework.web.context.support.``WebApplicationContextUtils.RequestObjectFactory

該對象跟ServletRequest.class做了映射關(guān)聯(lián)悉罕,放入了Spring管理bean注入的map中赤屋。

每次請求時误墓,如何保證自動注入的HttpServelet請求線程安全,等價于問 org.springframework.web.context.support.WebApplicationContextUtils.``RequestObjectFactory 怎么做到的線程安全益缎。這個類只是一個簡單的對象工廠,getObject方法最終從org.springframework.web.context.request.RequestContextHolder#requestAttributesHolder這個ThreadLocal中獲取請求的信息然想,由此做到了請求之間的隔離莺奔。

參考

https://stackoverflow.com/questions/3320674/spring-how-do-i-inject-an-httpservletrequest-into-a-request-scoped-bean

https://segmentfault.com/q/1010000040080836

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市变泄,隨后出現(xiàn)的幾起案子令哟,更是在濱河造成了極大的恐慌,老刑警劉巖妨蛹,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件屏富,死亡現(xiàn)場離奇詭異,居然都是意外死亡蛙卤,警方通過查閱死者的電腦和手機狠半,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來颤难,“玉大人神年,你說我怎么就攤上這事⌒朽停” “怎么了已日?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長栅屏。 經(jīng)常有香客問我飘千,道長,這世上最難降的妖魔是什么栈雳? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任护奈,我火速辦了婚禮,結(jié)果婚禮上甫恩,老公的妹妹穿的比我還像新娘逆济。我一直安慰自己,他們只是感情好磺箕,可當(dāng)我...
    茶點故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布奖慌。 她就那樣靜靜地躺著,像睡著了一般松靡。 火紅的嫁衣襯著肌膚如雪简僧。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天雕欺,我揣著相機與錄音岛马,去河邊找鬼棉姐。 笑死,一個胖子當(dāng)著我的面吹牛啦逆,可吹牛的內(nèi)容都是我干的伞矩。 我是一名探鬼主播,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼夏志,長吁一口氣:“原來是場噩夢啊……” “哼乃坤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起沟蔑,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤湿诊,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后瘦材,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體厅须,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年食棕,在試婚紗的時候發(fā)現(xiàn)自己被綠了朗和。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡簿晓,死狀恐怖例隆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情抢蚀,我是刑警寧澤镀层,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站皿曲,受9級特大地震影響唱逢,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜屋休,卻給世界環(huán)境...
    茶點故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一坞古、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧劫樟,春花似錦痪枫、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至附较,卻和暖如春吃粒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背拒课。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工徐勃, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留事示,地道東北人。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓僻肖,卻偏偏與公主長得像肖爵,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子臀脏,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,612評論 2 350

推薦閱讀更多精彩內(nèi)容