您好,登錄后才能下訂單哦!
本篇內(nèi)容介紹了“SpringMVC源碼分析之什么是FrameworkServlet”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
1.FrameworkServlet
FrameworkServlet 繼承自 HttpServletBean,而 HttpServletBean 繼承自 HttpServlet,HttpServlet 就是 JavaEE 里邊的東西了,這里我們不做討論,從 HttpServletBean 開始就是框架的東西了,但是 HttpServletBean 比較特殊,它的特殊在于它沒有進(jìn)行任何的請(qǐng)求處理,只是參與了一些初始化的操作,這些比較簡(jiǎn)單,而且我們?cè)谏掀恼轮幸惨呀?jīng)分析過(guò)了,所以這里我們對(duì) HttpServletBean 不做分析,就直接從它的子類 FrameworkServlet 開始看起。
和所有的 Servlet 一樣,F(xiàn)rameworkServlet 對(duì)請(qǐng)求的處理也是從 service 方法開始,我們先來(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)照了下,其他類型的請(qǐng)求統(tǒng)統(tǒng)都是 super.service 進(jìn)行處理。
然而在 HttpServlet 中并未對(duì) doGet、doPost 等請(qǐng)求進(jìn)行實(shí)質(zhì)性處理,所以 FrameworkServlet 中還重寫了各種請(qǐng)求對(duì)應(yīng)的方法,如 doDelete、doGet、doOptions、doPost、doPut、doTrace 等,其實(shí)就是除了 doHead 之外的其他方法都重寫了。
我們先來(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ì)不同類型的請(qǐng)求分類處理。
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)求還是交給父類去處理,由于 dispatchOptionsRequest 和 dispatchTraceRequest 變量默認(rèn)都是 false,因此默認(rèn)情況下,這兩種類型的請(qǐng)求都是交給了父類去處理。
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)容分為三部分:
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
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 類型的消息。
經(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í)不知道有沒有覺得眼熟,松哥在之前的 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)類,默認(rèn)使用的是 ServletRequestAttributes,通過(guò) ServletRequestAttributes,我們可以 getRequest、getResponse 以及 getSession。
在 ServletRequestAttributes 的具體實(shí)現(xiàn)中,會(huì)通過(guò) scope 參數(shù)判斷操作 request 還是操作 session(如果小伙伴們不記得 Spring 中的作用域問題,可以公號(hào)后臺(tái)回復(fù) spring,看看松哥錄制的免費(fèi)的 Spring 入門教程,里邊有講),我們來(lái)看一下 ServletRequestAttributes#setAttribute 方法(get/remove 方法執(zhí)行邏輯類似):
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 類似,RequestAttributes 被保存在 RequestContextHolder 中,RequestContextHolder 的原理也和 SecurityContextHolder 類似,這里不再贅述。
看了上面的講解,大家應(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 類型的事件,具體發(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)聽該事件,如下:
@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ā)。
“SpringMVC源碼分析之什么是FrameworkServlet”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。