自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

SpringMVC 源碼分析之 FrameworkServlet

開發(fā) 架構(gòu)
很多小伙伴都知道 SpringMVC 的核心是 DispatcherServlet,而 DispatcherServlet 的父類就是 FrameworkServlet,因此我們先來看看 FrameworkServlet,這有助于我們理解 DispatcherServlet。

[[389076]]

前面和小伙伴們聊了 SpringMVC 的初始化流程,相信大家對于 SpringMVC 的初始化過程都有一個基本認(rèn)知了,今天我們就來看看當(dāng)一個請求到達(dá)后,它的執(zhí)行流程是什么樣的?當(dāng)然這個流程比較長,松哥這里可能會分兩篇文章來和大家分享。

很多小伙伴都知道 SpringMVC 的核心是 DispatcherServlet,而 DispatcherServlet 的父類就是 FrameworkServlet,因此我們先來看看 FrameworkServlet,這有助于我們理解 DispatcherServlet。

1.FrameworkServlet

FrameworkServlet 繼承自 HttpServletBean,而 HttpServletBean 繼承自 HttpServlet,HttpServlet 就是 JavaEE 里邊的東西了,這里我們不做討論,從 HttpServletBean 開始就是框架的東西了,但是 HttpServletBean 比較特殊,它的特殊在于它沒有進(jìn)行任何的請求處理,只是參與了一些初始化的操作,這些比較簡單,而且我們在上篇文章中也已經(jīng)分析過了,所以這里我們對 HttpServletBean 不做分析,就直接從它的子類 FrameworkServlet 開始看起。

和所有的 Servlet 一樣,F(xiàn)rameworkServlet 對請求的處理也是從 service 方法開始,我們先來看看該方法 FrameworkServlet#service:

  1. @Override 
  2. protected void service(HttpServletRequest request, HttpServletResponse response) 
  3.   throws ServletException, IOException { 
  4.  HttpMethod httpMethod = HttpMethod.resolve(request.getMethod()); 
  5.  if (httpMethod == HttpMethod.PATCH || httpMethod == null) { 
  6.   processRequest(request, response); 
  7.  } 
  8.  else { 
  9.   super.service(request, response); 
  10.  } 

可以看到,在該方法中,首先獲取到當(dāng)前請求方法,然后對 patch 請求額外關(guān)照了下,其他類型的請求統(tǒng)統(tǒng)都是 super.service 進(jìn)行處理。

然而在 HttpServlet 中并未對 doGet、doPost 等請求進(jìn)行實(shí)質(zhì)性處理,所以 FrameworkServlet 中還重寫了各種請求對應(yīng)的方法,如 doDelete、doGet、doOptions、doPost、doPut、doTrace 等,其實(shí)就是除了 doHead 之外的其他方法都重寫了。

我們先來看看 doDelete、doGet、doPost 以及 doPut 四個方法:

  1. @Override 
  2. protected final void doGet(HttpServletRequest request, HttpServletResponse response) 
  3.   throws ServletException, IOException { 
  4.  processRequest(request, response); 
  5. @Override 
  6. protected final void doPost(HttpServletRequest request, HttpServletResponse response) 
  7.   throws ServletException, IOException { 
  8.  processRequest(request, response); 
  9. @Override 
  10. protected final void doPut(HttpServletRequest request, HttpServletResponse response) 
  11.   throws ServletException, IOException { 
  12.  processRequest(request, response); 
  13. @Override 
  14. protected final void doDelete(HttpServletRequest request, HttpServletResponse response) 
  15.   throws ServletException, IOException { 
  16.  processRequest(request, response); 

可以看到,這里又把請求交給 processRequest 去處理了,在 processRequest 方法中則會進(jìn)一步調(diào)用到 doService,對不同類型的請求分類處理。

doOptions 和 doTrace 則稍微有些差異,如下:

  1. @Override 
  2. protected void doOptions(HttpServletRequest request, HttpServletResponse response) 
  3.   throws ServletException, IOException { 
  4.  if (this.dispatchOptionsRequest || CorsUtils.isPreFlightRequest(request)) { 
  5.   processRequest(request, response); 
  6.   if (response.containsHeader("Allow")) { 
  7.    return
  8.   } 
  9.  } 
  10.  super.doOptions(request, new HttpServletResponseWrapper(response) { 
  11.   @Override 
  12.   public void setHeader(String name, String value) { 
  13.    if ("Allow".equals(name)) { 
  14.     value = (StringUtils.hasLength(value) ? value + ", " : "") + HttpMethod.PATCH.name(); 
  15.    } 
  16.    super.setHeader(name, value); 
  17.   } 
  18.  }); 
  19. @Override 
  20. protected void doTrace(HttpServletRequest request, HttpServletResponse response) 
  21.   throws ServletException, IOException { 
  22.  if (this.dispatchTraceRequest) { 
  23.   processRequest(request, response); 
  24.   if ("message/http".equals(response.getContentType())) { 
  25.    return
  26.   } 
  27.  } 
  28.  super.doTrace(request, response); 

可以看到這兩個方法的處理多了一層邏輯,就是去選擇是在當(dāng)前方法中處理對應(yīng)的請求還是交給父類去處理,由于 dispatchOptionsRequest 和 dispatchTraceRequest 變量默認(rèn)都是 false,因此默認(rèn)情況下,這兩種類型的請求都是交給了父類去處理。

2.processRequest

我們再來看 processRequest,這算是 FrameworkServlet 的核心方法了:

  1. protected final void processRequest(HttpServletRequest request, HttpServletResponse response) 
  2.   throws ServletException, IOException { 
  3.  long startTime = System.currentTimeMillis(); 
  4.  Throwable failureCause = null
  5.  LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext(); 
  6.  LocaleContext localeContext = buildLocaleContext(request); 
  7.  RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes(); 
  8.  ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes); 
  9.  WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); 
  10.  asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor()); 
  11.  initContextHolders(request, localeContext, requestAttributes); 
  12.  try { 
  13.   doService(request, response); 
  14.  } 
  15.  catch (ServletException | IOException ex) { 
  16.   failureCause = ex; 
  17.   throw ex; 
  18.  } 
  19.  catch (Throwable ex) { 
  20.   failureCause = ex; 
  21.   throw new NestedServletException("Request processing failed", ex); 
  22.  } 
  23.  finally { 
  24.   resetContextHolders(request, previousLocaleContext, previousAttributes); 
  25.   if (requestAttributes != null) { 
  26.    requestAttributes.requestCompleted(); 
  27.   } 
  28.   logResult(request, response, failureCause, asyncManager); 
  29.   publishRequestHandledEvent(request, response, startTime, failureCause); 
  30.  } 

這個方法雖然比較長,但是其實(shí)它的核心就是最中間的 doService 方法,以 doService 為界,我們可以將該方法的內(nèi)容分為三部分:

  1. doService 之前主要是一些準(zhǔn)備工作,準(zhǔn)備工作主要干了兩件事,第一件事就是從 LocaleContextHolder 和 RequestContextHolder 中分別獲取它們原來保存的 LocaleContext 和 RequestAttributes 對象存起來,然后分別調(diào)用 buildLocaleContext 和 buildRequestAttributes 方法獲取到當(dāng)前請求的 LocaleContext 和 RequestAttributes 對象,再通過 initContextHolders 方法將當(dāng)前請求的 LocaleContext 和 RequestAttributes 對象分別設(shè)置到 LocaleContextHolder 和 RequestContextHolder 對象中;第二件事則是獲取到異步管理器并設(shè)置攔截器。
  2. 接下來就是 doService 方法,這是一個抽象方法,具體的實(shí)現(xiàn)在 DispatcherServlet 中,這個松哥放到 DispatcherServlet 中再和大家分析。
  3. 第三部分就是 finally 中,這個里邊干了兩件事:第一件事就是將 LocaleContextHolder 和 RequestContextHolder 中對應(yīng)的對象恢復(fù)成原來的樣子(參考第一步);第二件事就是通過 publishRequestHandledEvent 方法發(fā)布一個 ServletRequestHandledEvent 類型的消息。

經(jīng)過上面的分析,大家發(fā)現(xiàn),processRequest 其實(shí)主要做了兩件事,第一件事就是對 LocaleContext 和 RequestAttributes 的處理,第二件事就是發(fā)布事件。我們對這兩件事分別來研究。

2.1 LocaleContext 和 RequestAttributes

LocaleContext 和 RequestAttributes 都是接口,不同的是里邊存放的對象不同。

2.1.1 LocaleContext

LocaleContext 里邊存放著 Locale,也就是本地化信息,如果我們需要支持國際化,就會用到 Locale。

國際化的時候,如果我們需要用到 Locale 對象,第一反應(yīng)就是從 HttpServletRequest 中獲取,像下面這樣:

  1. Locale locale = req.getLocale(); 

但是大家知道,HttpServletRequest 只存在于 Controller 中,如果我們想要在 Service 層獲取 HttpServletRequest,就得從 Controller 中傳參數(shù)過來,這樣就比較麻煩,特別是有的時候 Service 中相關(guān)方法都已經(jīng)定義好了再去修改,就更頭大了。

所以 SpringMVC 中還給我們提供了 LocaleContextHolder,這個工具就是用來保存當(dāng)前請求的 LocaleContext 的。當(dāng)大家看到 LocaleContextHolder 時不知道有沒有覺得眼熟,松哥在之前的 Spring Security 系列教程中和大家聊過 SecurityContextHolder,這兩個的原理基本一致,都是基于 ThreadLocal 來保存變量,進(jìn)而確保不同線程之間互不干擾,對 ThreadLocal 不熟悉的小伙伴,可以看看松哥的 Spring Security 系列,之前有詳細(xì)分析過(公號后臺回復(fù) ss)。

有了 LocaleContextHolder 之后,我們就可以在任何地方獲取 Locale 了,例如在 Service 中我們可以通過如下方式獲取 Locale:

  1. Locale locale = LocaleContextHolder.getLocale(); 

上面這個 Locale 對象實(shí)際上就是從 LocaleContextHolder 中的 LocaleContext 里邊取出來的。

需要注意的是,SpringMVC 中還有一個 LocaleResolver 解析器,所以前面 req.getLocale() 并不總是獲取到 Locale 的值,這個松哥在以后的文章中再和小伙伴們細(xì)聊。

2.1.2 RequestAttributes

RequestAttributes 是一個接口,這個接口可以用來 get/set/remove 某一個屬性。

RequestAttributes 有諸多實(shí)現(xiàn)類,默認(rèn)使用的是 ServletRequestAttributes,通過 ServletRequestAttributes,我們可以 getRequest、getResponse 以及 getSession。

在 ServletRequestAttributes 的具體實(shí)現(xiàn)中,會通過 scope 參數(shù)判斷操作 request 還是操作 session(如果小伙伴們不記得 Spring 中的作用域問題,可以公號后臺回復(fù) spring,看看松哥錄制的免費(fèi)的 Spring 入門教程,里邊有講),我們來看一下 ServletRequestAttributes#setAttribute 方法(get/remove 方法執(zhí)行邏輯類似):

  1. public void setAttribute(String name, Object value, int scope) { 
  2.     if (scope == 0) { 
  3.         if (!this.isRequestActive()) { 
  4.             throw new IllegalStateException("Cannot set request attribute - request is not active anymore!"); 
  5.         } 
  6.         this.request.setAttribute(name, value); 
  7.     } else { 
  8.         HttpSession session = this.obtainSession(); 
  9.         this.sessionAttributesToUpdate.remove(name); 
  10.         session.setAttribute(name, value); 
  11.     } 

可以看到,這里會先判斷 scope,scope 為 0 就操作 request,scope 為 1 就操作 session。如果操作的是 request,則需要首先通過 isRequestActive 方法判斷當(dāng)前 request 是否執(zhí)行完畢,如果執(zhí)行完畢,就不可以再對其進(jìn)行其他操作了(當(dāng)執(zhí)行了 finally 代碼塊中的 requestAttributes.requestCompleted 方法后,isRequestActive 就會返回 false)。

和 LocaleContext 類似,RequestAttributes 被保存在 RequestContextHolder 中,RequestContextHolder 的原理也和 SecurityContextHolder 類似,這里不再贅述。

看了上面的講解,大家應(yīng)該發(fā)現(xiàn)了,在 SpringMVC 中,如果我們需要在 Controller 之外的其他地方使用 request、response 以及 session,其實(shí)不用每次都從 Controller 中傳遞 request、response 以及 session 等對象,我們完全可以直接通過 RequestContextHolder 來獲取,像下面這樣:

  1. ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); 
  2. HttpServletRequest request = servletRequestAttributes.getRequest(); 
  3. HttpServletResponse response = servletRequestAttributes.getResponse(); 

是不是非常 easy!

2.2 事件發(fā)布

最后就是 processRequest 方法中的事件發(fā)布了。

在 finally 代碼塊中會調(diào)用 publishRequestHandledEvent 方法發(fā)送一個 ServletRequestHandledEvent 類型的事件,具體發(fā)送代碼如下:

  1. private void publishRequestHandledEvent(HttpServletRequest request, HttpServletResponse response, 
  2.   long startTime, @Nullable Throwable failureCause) { 
  3.  if (this.publishEvents && this.webApplicationContext != null) { 
  4.   // Whether or not we succeeded, publish an event. 
  5.   long processingTime = System.currentTimeMillis() - startTime; 
  6.   this.webApplicationContext.publishEvent( 
  7.     new ServletRequestHandledEvent(this, 
  8.       request.getRequestURI(), request.getRemoteAddr(), 
  9.       request.getMethod(), getServletConfig().getServletName(), 
  10.       WebUtils.getSessionId(request), getUsernameForRequest(request), 
  11.       processingTime, failureCause, response.getStatus())); 
  12.  } 

可以看到,事件的發(fā)送需要 publishEvents 為 true,而該變量默認(rèn)就是 true。如果需要修改該變量的值,可以在 web.xml 中配置 DispatcherServlet 時,通過 init-param 節(jié)點(diǎn)順便配置一下該變量的值。正常情況下,這個事件總是會被發(fā)送出去,如果項(xiàng)目有需要,我們可以監(jiān)聽該事件,如下:

  1. @Component 
  2. public class ServletRequestHandleListener implements ApplicationListener<ServletRequestHandledEvent> { 
  3.     @Override 
  4.     public void onApplicationEvent(ServletRequestHandledEvent servletRequestHandledEvent) { 
  5.         System.out.println("請求執(zhí)行完畢-"+servletRequestHandledEvent.getRequestUrl()); 
  6.     } 

當(dāng)一個請求執(zhí)行完畢時,該事件就會被觸發(fā)。

3.小結(jié)

這篇文章主要和小伙伴們分享了 SpringMVC 中 DispatcherServlet 的父類 FrameworkServlet,F(xiàn)rameworkServlet 的功能其實(shí)比較簡單,主要就是在 service 方法中增加了對 PATCH 的處理,然后其他類型的請求都被歸類到 processRequest 方法中進(jìn)行統(tǒng)一處理,processRequest 方法則又分了三部分,首先是對 LocaleContext 和 RequestAttributes 的處理,然后執(zhí)行 doService,最后在 finally 代碼塊中對 LocaleContext 和 RequestAttributes 屬性進(jìn)行復(fù)原,同時發(fā)布一個請求結(jié)束的事件。

本文轉(zhuǎn)載自微信公眾號「江南一點(diǎn)雨」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系江南一點(diǎn)雨公眾號。

 

責(zé)任編輯:武曉燕 來源: 江南一點(diǎn)雨
相關(guān)推薦

2011-05-26 10:05:48

MongoDB

2023-02-26 08:42:10

源碼demouseEffect

2012-09-20 10:07:29

Nginx源碼分析Web服務(wù)器

2021-07-06 09:29:38

Cobar源碼AST

2024-06-13 07:55:19

2011-05-26 16:18:51

Mongodb

2021-03-26 11:00:50

SpringMVC組件接口

2020-07-28 08:54:39

內(nèi)核通信Netlink

2012-09-06 10:07:26

jQuery

2021-09-05 07:35:58

lifecycleAndroid組件原理

2009-07-08 13:22:30

JDK源碼分析Set

2017-01-12 14:52:03

JVMFinalRefere源碼

2022-08-27 08:02:09

SQL函數(shù)語法

2022-01-06 07:06:52

KubernetesResourceAPI

2022-05-30 07:36:54

vmstoragevmselect

2019-09-09 06:30:06

Springboot程序員開發(fā)

2023-03-17 07:53:20

K8sAPIServerKubernetes

2017-02-27 11:48:58

JVM源碼分析Java

2021-02-16 10:55:02

Nodejs模塊

2020-05-26 18:50:46

JVMAttachJava
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號