溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

SpringMVC源碼分析之什么是FrameworkServlet

發(fā)布時(shí)間:2021-10-15 13:46:36 來(lái)源:億速云 閱讀:126 作者:iii 欄目:開發(fā)技術(shù)

本篇內(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 開始看起。

SpringMVC源碼分析之什么是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)容分為三部分:

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. 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è)置攔截器。

  3. 接下來(lái)就是 doService 方法,這是一個(gè)抽象方法,具體的實(shí)現(xiàn)在 DispatcherServlet 中,這個(gè)松哥放到  DispatcherServlet 中再和大家分析。

  4. 第三部分就是 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í)用文章!

向AI問一下細(xì)節(jié)

免責(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)容。

AI