成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

SpringMVC 源碼分析之 FrameworkServlet

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

[[389076]]

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

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

1.FrameworkServlet

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

和所有的 Servlet 一樣,FrameworkServlet 對請求的處理也是從 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.  } 

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

然而在 HttpServlet 中并未對 doGet、doPost 等請求進行實質性處理,所以 FrameworkServlet 中還重寫了各種請求對應的方法,如 doDelete、doGet、doOptions、doPost、doPut、doTrace 等,其實就是除了 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 方法中則會進一步調用到 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); 

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

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.  } 

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

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

經過上面的分析,大家發現,processRequest 其實主要做了兩件事,第一件事就是對 LocaleContext 和 RequestAttributes 的處理,第二件事就是發布事件。我們對這兩件事分別來研究。

2.1 LocaleContext 和 RequestAttributes

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

2.1.1 LocaleContext

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

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

  1. Locale locale = req.getLocale(); 

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

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

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

  1. Locale locale = LocaleContextHolder.getLocale(); 

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

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

2.1.2 RequestAttributes

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

RequestAttributes 有諸多實現類,默認使用的是 ServletRequestAttributes,通過 ServletRequestAttributes,我們可以 getRequest、getResponse 以及 getSession。

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

  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 方法判斷當前 request 是否執行完畢,如果執行完畢,就不可以再對其進行其他操作了(當執行了 finally 代碼塊中的 requestAttributes.requestCompleted 方法后,isRequestActive 就會返回 false)。

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

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

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

是不是非常 easy!

2.2 事件發布

最后就是 processRequest 方法中的事件發布了。

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

  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.  } 

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

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

當一個請求執行完畢時,該事件就會被觸發。

3.小結

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

本文轉載自微信公眾號「江南一點雨」,可以通過以下二維碼關注。轉載本文請聯系江南一點雨公眾號。

 

責任編輯:武曉燕 來源: 江南一點雨
相關推薦

2011-05-26 10:05:48

MongoDB

2012-09-20 10:07:29

Nginx源碼分析Web服務器

2023-02-26 08:42:10

源碼demouseEffect

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

內核通信Netlink

2012-09-06 10:07:26

jQuery

2021-09-05 07:35:58

lifecycleAndroid組件原理

2009-07-08 13:22:30

JDK源碼分析Set

2022-01-06 07:06:52

KubernetesResourceAPI

2022-08-27 08:02:09

SQL函數語法

2022-05-30 07:36:54

vmstoragevmselect

2017-01-12 14:52:03

JVMFinalRefere源碼

2019-09-09 06:30:06

Springboot程序員開發

2023-03-17 07:53:20

K8sAPIServerKubernetes

2022-07-06 10:37:45

SpringServlet初始化

2017-02-27 11:48:58

JVM源碼分析Java

2021-02-16 10:55:02

Nodejs模塊
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 欧美成人a | 欧美淫| 国产激情视频在线观看 | 国产成人福利视频在线观看 | 日韩影音| 国产人成精品一区二区三 | 日韩色在线 | 午夜免费在线观看 | 九九av | 日韩精品一区在线观看 | 精品国产一区二区三区免费 | 亚洲精品久久久久中文字幕二区 | 中文字幕人成乱码在线观看 | 香蕉91| 男女羞羞免费视频 | 伊人狠狠干 | 精品一二 | 91国在线视频 | 美女福利视频 | 亚洲激情一区二区三区 | 性色av网站 | 亚洲精品www | 精品欧美一区二区三区久久久 | 久久久一区二区三区 | 国产精品久久久久一区二区三区 | 日韩中文字幕网 | 久久久久久久久久久91 | 欧美一区视频在线 | 成人激情视频在线 | 欧美中文字幕一区 | 亚洲福利av| 久久99精品久久久久久国产越南 | 久久精品国产久精国产 | 国产激情视频在线观看 | 男女精品网站 | 日韩中文字幕视频在线 | 成人免费在线视频 | 亚洲国产网址 | 日韩中文字幕一区 | 国产综合在线视频 | 欧美激情视频一区二区三区免费 |