問題
在最近一次團隊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
實例化之后的情況壤靶。
由上圖我們可以看到這個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)注亚茬。
ConfigurableListableBeanFactory
的registerResolvableDependency
方法又是用什么的呢?我們繼續(xù)往下看亲铡。
使用相應(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中。
疑慮2
HttpServletRequest注入和引用我們知道了姑尺,那么針對每個獨立的請求竟终,多線程場景下,通過自動注入的方式切蟋,HttpServletRequest 是否會有線程安全的問題呢统捶?
我們已經(jīng)知道注入拿到的HttpServletRequest返回的對象其實是下面這個
org.springframework.web.context.support.WebApplicationContextUtils.``RequestObjectFactory
對象,那么它是怎么做到線程安全的呢柄粹?
我們看它的具體實現(xiàn)喘鸟。
沿著currentRequestAttributes
()
方法一路點擊,最終發(fā)現(xiàn)它的返回值镰惦,是來自org.springframework.web.context.request.RequestContextHolder#requestAttributesHolder
迷守。
requestAttributesHolder
對象是一個ThreadLocal對象,由此我們明白了旺入,Spring自動注入的HttpServletRequest能夠保證請求的唯一性兑凿,原來是通過跟每個線程綁定了,從ThreadLocal中取得請求信息茵瘾,由此保證的線程安全礼华!
我們再來拓展一下,既然請求信息是從ThreadLocal中取的拗秘,那么請求信息是如何放進去的呢圣絮?
我們找到requestAttributesHolder
屬性的set方法,在這里打一個斷點雕旨,重啟系統(tǒng)扮匠,并發(fā)起一個http請求。
最終凡涩,通過調(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中獲取請求的信息然想,由此做到了請求之間的隔離莺奔。