SpringMVC 源碼分析之 FrameworkServlet
前面和小伙伴們聊了 SpringMVC 的初始化流程,相信大家對(duì)于 SpringMVC 的初始化過(guò)程都有一個(gè)基本認(rèn)知了,今天我們就來(lái)看看當(dāng)一個(gè)請(qǐng)求到達(dá)后,它的執(zhí)行流程是什么樣的?當(dāng)然這個(gè)流程比較長(zhǎng),松哥這里可能會(huì)分兩篇文章來(lái)和大家分享。
很多小伙伴都知道 SpringMVC 的核心是 DispatcherServlet,而 DispatcherServlet 的父類(lèi)就是 FrameworkServlet,因此我們先來(lái)看看 FrameworkServlet,這有助于我們理解 DispatcherServlet。
1.FrameworkServlet
FrameworkServlet 繼承自 HttpServletBean,而 HttpServletBean 繼承自 HttpServlet,HttpServlet 就是 JavaEE 里邊的東西了,這里我們不做討論,從 HttpServletBean 開(kāi)始就是框架的東西了,但是 HttpServletBean 比較特殊,它的特殊在于它沒(méi)有進(jìn)行任何的請(qǐng)求處理,只是參與了一些初始化的操作,這些比較簡(jiǎn)單,而且我們?cè)谏掀恼轮幸惨呀?jīng)分析過(guò)了,所以這里我們對(duì) HttpServletBean 不做分析,就直接從它的子類(lèi) FrameworkServlet 開(kāi)始看起。
和所有的 Servlet 一樣,F(xiàn)rameworkServlet 對(duì)請(qǐng)求的處理也是從 service 方法開(kāi)始,我們先來(lái)看看該方法 FrameworkServlet#service:
- @Override
- protected void service(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
- if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
- processRequest(request, response);
- }
- else {
- super.service(request, response);
- }
- }
可以看到,在該方法中,首先獲取到當(dāng)前請(qǐng)求方法,然后對(duì) patch 請(qǐng)求額外關(guān)照了下,其他類(lèi)型的請(qǐng)求統(tǒng)統(tǒng)都是 super.service 進(jìn)行處理。
然而在 HttpServlet 中并未對(duì) doGet、doPost 等請(qǐng)求進(jìn)行實(shí)質(zhì)性處理,所以 FrameworkServlet 中還重寫(xiě)了各種請(qǐng)求對(duì)應(yīng)的方法,如 doDelete、doGet、doOptions、doPost、doPut、doTrace 等,其實(shí)就是除了 doHead 之外的其他方法都重寫(xiě)了。
我們先來(lái)看看 doDelete、doGet、doPost 以及 doPut 四個(gè)方法:
- @Override
- protected final void doGet(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- processRequest(request, response);
- }
- @Override
- protected final void doPost(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- processRequest(request, response);
- }
- @Override
- protected final void doPut(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- processRequest(request, response);
- }
- @Override
- protected final void doDelete(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- processRequest(request, response);
- }
可以看到,這里又把請(qǐng)求交給 processRequest 去處理了,在 processRequest 方法中則會(huì)進(jìn)一步調(diào)用到 doService,對(duì)不同類(lèi)型的請(qǐng)求分類(lèi)處理。
doOptions 和 doTrace 則稍微有些差異,如下:
- @Override
- protected void doOptions(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- if (this.dispatchOptionsRequest || CorsUtils.isPreFlightRequest(request)) {
- processRequest(request, response);
- if (response.containsHeader("Allow")) {
- return;
- }
- }
- super.doOptions(request, new HttpServletResponseWrapper(response) {
- @Override
- public void setHeader(String name, String value) {
- if ("Allow".equals(name)) {
- value = (StringUtils.hasLength(value) ? value + ", " : "") + HttpMethod.PATCH.name();
- }
- super.setHeader(name, value);
- }
- });
- }
- @Override
- protected void doTrace(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- if (this.dispatchTraceRequest) {
- processRequest(request, response);
- if ("message/http".equals(response.getContentType())) {
- return;
- }
- }
- super.doTrace(request, response);
- }
可以看到這兩個(gè)方法的處理多了一層邏輯,就是去選擇是在當(dāng)前方法中處理對(duì)應(yīng)的請(qǐng)求還是交給父類(lèi)去處理,由于 dispatchOptionsRequest 和 dispatchTraceRequest 變量默認(rèn)都是 false,因此默認(rèn)情況下,這兩種類(lèi)型的請(qǐng)求都是交給了父類(lèi)去處理。
2.processRequest
我們?cè)賮?lái)看 processRequest,這算是 FrameworkServlet 的核心方法了:
- protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- long startTime = System.currentTimeMillis();
- Throwable failureCause = null;
- LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
- LocaleContext localeContext = buildLocaleContext(request);
- RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
- ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
- WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
- asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
- initContextHolders(request, localeContext, requestAttributes);
- try {
- doService(request, response);
- }
- catch (ServletException | IOException ex) {
- failureCause = ex;
- throw ex;
- }
- catch (Throwable ex) {
- failureCause = ex;
- throw new NestedServletException("Request processing failed", ex);
- }
- finally {
- resetContextHolders(request, previousLocaleContext, previousAttributes);
- if (requestAttributes != null) {
- requestAttributes.requestCompleted();
- }
- logResult(request, response, failureCause, asyncManager);
- publishRequestHandledEvent(request, response, startTime, failureCause);
- }
- }
這個(gè)方法雖然比較長(zhǎng),但是其實(shí)它的核心就是最中間的 doService 方法,以 doService 為界,我們可以將該方法的內(nèi)容分為三部分:
- doService 之前主要是一些準(zhǔn)備工作,準(zhǔn)備工作主要干了兩件事,第一件事就是從 LocaleContextHolder 和 RequestContextHolder 中分別獲取它們?cè)瓉?lái)保存的 LocaleContext 和 RequestAttributes 對(duì)象存起來(lái),然后分別調(diào)用 buildLocaleContext 和 buildRequestAttributes 方法獲取到當(dāng)前請(qǐng)求的 LocaleContext 和 RequestAttributes 對(duì)象,再通過(guò) initContextHolders 方法將當(dāng)前請(qǐng)求的 LocaleContext 和 RequestAttributes 對(duì)象分別設(shè)置到 LocaleContextHolder 和 RequestContextHolder 對(duì)象中;第二件事則是獲取到異步管理器并設(shè)置攔截器。
- 接下來(lái)就是 doService 方法,這是一個(gè)抽象方法,具體的實(shí)現(xiàn)在 DispatcherServlet 中,這個(gè)松哥放到 DispatcherServlet 中再和大家分析。
- 第三部分就是 finally 中,這個(gè)里邊干了兩件事:第一件事就是將 LocaleContextHolder 和 RequestContextHolder 中對(duì)應(yīng)的對(duì)象恢復(fù)成原來(lái)的樣子(參考第一步);第二件事就是通過(guò) publishRequestHandledEvent 方法發(fā)布一個(gè) ServletRequestHandledEvent 類(lèi)型的消息。
經(jīng)過(guò)上面的分析,大家發(fā)現(xiàn),processRequest 其實(shí)主要做了兩件事,第一件事就是對(duì) LocaleContext 和 RequestAttributes 的處理,第二件事就是發(fā)布事件。我們對(duì)這兩件事分別來(lái)研究。
2.1 LocaleContext 和 RequestAttributes
LocaleContext 和 RequestAttributes 都是接口,不同的是里邊存放的對(duì)象不同。
2.1.1 LocaleContext
LocaleContext 里邊存放著 Locale,也就是本地化信息,如果我們需要支持國(guó)際化,就會(huì)用到 Locale。
國(guó)際化的時(shí)候,如果我們需要用到 Locale 對(duì)象,第一反應(yīng)就是從 HttpServletRequest 中獲取,像下面這樣:
- Locale locale = req.getLocale();
但是大家知道,HttpServletRequest 只存在于 Controller 中,如果我們想要在 Service 層獲取 HttpServletRequest,就得從 Controller 中傳參數(shù)過(guò)來(lái),這樣就比較麻煩,特別是有的時(shí)候 Service 中相關(guān)方法都已經(jīng)定義好了再去修改,就更頭大了。
所以 SpringMVC 中還給我們提供了 LocaleContextHolder,這個(gè)工具就是用來(lái)保存當(dāng)前請(qǐng)求的 LocaleContext 的。當(dāng)大家看到 LocaleContextHolder 時(shí)不知道有沒(méi)有覺(jué)得眼熟,松哥在之前的 Spring Security 系列教程中和大家聊過(guò) SecurityContextHolder,這兩個(gè)的原理基本一致,都是基于 ThreadLocal 來(lái)保存變量,進(jìn)而確保不同線程之間互不干擾,對(duì) ThreadLocal 不熟悉的小伙伴,可以看看松哥的 Spring Security 系列,之前有詳細(xì)分析過(guò)(公號(hào)后臺(tái)回復(fù) ss)。
有了 LocaleContextHolder 之后,我們就可以在任何地方獲取 Locale 了,例如在 Service 中我們可以通過(guò)如下方式獲取 Locale:
- Locale locale = LocaleContextHolder.getLocale();
上面這個(gè) Locale 對(duì)象實(shí)際上就是從 LocaleContextHolder 中的 LocaleContext 里邊取出來(lái)的。
需要注意的是,SpringMVC 中還有一個(gè) LocaleResolver 解析器,所以前面 req.getLocale() 并不總是獲取到 Locale 的值,這個(gè)松哥在以后的文章中再和小伙伴們細(xì)聊。
2.1.2 RequestAttributes
RequestAttributes 是一個(gè)接口,這個(gè)接口可以用來(lái) get/set/remove 某一個(gè)屬性。
RequestAttributes 有諸多實(shí)現(xiàn)類(lèi),默認(rèn)使用的是 ServletRequestAttributes,通過(guò) ServletRequestAttributes,我們可以 getRequest、getResponse 以及 getSession。
在 ServletRequestAttributes 的具體實(shí)現(xiàn)中,會(huì)通過(guò) scope 參數(shù)判斷操作 request 還是操作 session(如果小伙伴們不記得 Spring 中的作用域問(wèn)題,可以公號(hào)后臺(tái)回復(fù) spring,看看松哥錄制的免費(fèi)的 Spring 入門(mén)教程,里邊有講),我們來(lái)看一下 ServletRequestAttributes#setAttribute 方法(get/remove 方法執(zhí)行邏輯類(lèi)似):
- public void setAttribute(String name, Object value, int scope) {
- if (scope == 0) {
- if (!this.isRequestActive()) {
- throw new IllegalStateException("Cannot set request attribute - request is not active anymore!");
- }
- this.request.setAttribute(name, value);
- } else {
- HttpSession session = this.obtainSession();
- this.sessionAttributesToUpdate.remove(name);
- session.setAttribute(name, value);
- }
- }
可以看到,這里會(huì)先判斷 scope,scope 為 0 就操作 request,scope 為 1 就操作 session。如果操作的是 request,則需要首先通過(guò) isRequestActive 方法判斷當(dāng)前 request 是否執(zhí)行完畢,如果執(zhí)行完畢,就不可以再對(duì)其進(jìn)行其他操作了(當(dāng)執(zhí)行了 finally 代碼塊中的 requestAttributes.requestCompleted 方法后,isRequestActive 就會(huì)返回 false)。
和 LocaleContext 類(lèi)似,RequestAttributes 被保存在 RequestContextHolder 中,RequestContextHolder 的原理也和 SecurityContextHolder 類(lèi)似,這里不再贅述。
看了上面的講解,大家應(yīng)該發(fā)現(xiàn)了,在 SpringMVC 中,如果我們需要在 Controller 之外的其他地方使用 request、response 以及 session,其實(shí)不用每次都從 Controller 中傳遞 request、response 以及 session 等對(duì)象,我們完全可以直接通過(guò) RequestContextHolder 來(lái)獲取,像下面這樣:
- ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
- HttpServletRequest request = servletRequestAttributes.getRequest();
- HttpServletResponse response = servletRequestAttributes.getResponse();
是不是非常 easy!
2.2 事件發(fā)布
最后就是 processRequest 方法中的事件發(fā)布了。
在 finally 代碼塊中會(huì)調(diào)用 publishRequestHandledEvent 方法發(fā)送一個(gè) ServletRequestHandledEvent 類(lèi)型的事件,具體發(fā)送代碼如下:
- private void publishRequestHandledEvent(HttpServletRequest request, HttpServletResponse response,
- long startTime, @Nullable Throwable failureCause) {
- if (this.publishEvents && this.webApplicationContext != null) {
- // Whether or not we succeeded, publish an event.
- long processingTime = System.currentTimeMillis() - startTime;
- this.webApplicationContext.publishEvent(
- new ServletRequestHandledEvent(this,
- request.getRequestURI(), request.getRemoteAddr(),
- request.getMethod(), getServletConfig().getServletName(),
- WebUtils.getSessionId(request), getUsernameForRequest(request),
- processingTime, failureCause, response.getStatus()));
- }
- }
可以看到,事件的發(fā)送需要 publishEvents 為 true,而該變量默認(rèn)就是 true。如果需要修改該變量的值,可以在 web.xml 中配置 DispatcherServlet 時(shí),通過(guò) init-param 節(jié)點(diǎn)順便配置一下該變量的值。正常情況下,這個(gè)事件總是會(huì)被發(fā)送出去,如果項(xiàng)目有需要,我們可以監(jiān)聽(tīng)該事件,如下:
- @Component
- public class ServletRequestHandleListener implements ApplicationListener<ServletRequestHandledEvent> {
- @Override
- public void onApplicationEvent(ServletRequestHandledEvent servletRequestHandledEvent) {
- System.out.println("請(qǐng)求執(zhí)行完畢-"+servletRequestHandledEvent.getRequestUrl());
- }
- }
當(dāng)一個(gè)請(qǐng)求執(zhí)行完畢時(shí),該事件就會(huì)被觸發(fā)。
3.小結(jié)
這篇文章主要和小伙伴們分享了 SpringMVC 中 DispatcherServlet 的父類(lèi) FrameworkServlet,F(xiàn)rameworkServlet 的功能其實(shí)比較簡(jiǎn)單,主要就是在 service 方法中增加了對(duì) PATCH 的處理,然后其他類(lèi)型的請(qǐng)求都被歸類(lèi)到 processRequest 方法中進(jìn)行統(tǒng)一處理,processRequest 方法則又分了三部分,首先是對(duì) LocaleContext 和 RequestAttributes 的處理,然后執(zhí)行 doService,最后在 finally 代碼塊中對(duì) LocaleContext 和 RequestAttributes 屬性進(jìn)行復(fù)原,同時(shí)發(fā)布一個(gè)請(qǐng)求結(jié)束的事件。
本文轉(zhuǎn)載自微信公眾號(hào)「江南一點(diǎn)雨」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系江南一點(diǎn)雨公眾號(hào)。