SpringMVC源碼閱讀:核心分發器DispatcherServlet


1.前言

SpringMVC是目前J2EE平台的主流Web框架,不熟悉的園友可以看SpringMVC源碼閱讀入門,它交代了SpringMVC的基礎知識和源碼閱讀的技巧

本文將介紹SpringMVC的核心分發器DispatcherServlet,通過源碼分析DispatcherServlet的運行過程

2.DispatcherServlet的初始化

首先打開DispatcherServlet類繼承圖

可以看到,DispatcherServlet繼承自HttpServlet,它的本質就是一個Servlet,這就是為什么上篇需要在web.xml通過url-mapping為DispatcherServlet配置映射請求的原因

我們從HttpServletBean開始看,HttpServletBean重寫了其父類GenericServlet的init方法,我們來看看init到底做了什么(在啟動Tomcat的時候會進入init方法)

ServletConfigPropertyValues是HttpServletBean的內部靜態類,它負責取到web.xml中contextConfigLocation,並addPropertyValue(),在PropertyValues可以看到取到的值

BeanWrapper是一個實體包裝類,簡單地說,BeanWrapper提供分析和操作JavaBean的方案,如值的set/get方法、描述的set/get方法以及屬性的可讀可寫性

ResourceLoader讀取到servletContext和classLoader,servletContext裝載了我們剛才的dispatcher-servlet.xml,classLoader找到我們的字節碼文件並追蹤到我們的jar包路徑,還有很多屬性不一一介紹,園友們可以自行打斷點查看

web.xml部分代碼,這就是我們讀取的contextConfigLocation

<servlet>
  <servlet-name>dispatcher</servlet-name>  
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
  <load-on-startup>1</load-on-startup>  
  <init-param>
    <param-name>contextConfigLocation</param-name>  
    <param-value>classpath:springConfig/dispatcher-servlet.xml</param-value>  
  </init-param>
</servlet>

<servlet-mapping>
  <servlet-name>dispatcher</servlet-name>  
  <url-pattern>/</url-pattern>  
</servlet-mapping>

 

回頭再看,這里構造BeanWrapper,使用setPropertyValues設置PropertyValues,利用Spring依賴注入的特性初始化屬性,讀取web.xml的contextConfigLocation屬性用於構造Spring上下文

用BeanWrapper的最大好處在於,我們不需要再在HttpServletBean中定義contextConfigLocation屬性,並聲明調用set/get方法,BeanWrapper已經幫我們做好了

 

按ctrl+alt+b,看initServletBean()到底在哪里被實現

 

 接下來,我們看FrameworkServlet這個類,該類繼承自HttpServletBean,看FrameworkServlet的initServletBean()方法

webApplicationContext是FrameworkServlet的上下文,initWebApplicationContext()方法為當前Servlet初始化上下文

initFrameworkServlet()交由FrameworkServlet子類實現,默認實現為空,該方法會在bean屬性和上下文加載后被調用

我們現在看initWebApplicationContext()方法實現

獲取根上下文,並初始化一個空的上下文

527行不會進入if,只有上下文實例在構造的時候注入才會調用

549行調用findWebApplicationContext()方法,這個方法用來查看該Servlet是否已經設置上下文,我們點進去看,沒有得到attrName,返回null

當FrameworkServlet沒有上下文實例定義時,調用createWebApplicationContext(),參數是我們在initWebApplicationContext()中得到的rootContext(根上下文),為FrameworkServlet初始化上下文,設置id,environment,configLocation等屬性

560行onRefresh()是為了防止構造注入上下文的時候沒有刷新,去手動刷新,在DispatcherServlet有實現

566行為當前Servlet設置上下文

web.xml中配置的ContextLoaderListener根據applicationContext.xml生成上下文

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:springConfig/dispatcher-servlet.xml</param-value>
    </context-param>

    <!-- Bootstrap the root application context as usual using ContextLoaderListener -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

 進入ContextLoaderListener,打開其父類ContextLoader,經常用SpringMVC開發的人應該對ClassPathResource比較熟悉,ClassPathResource經常被我們用來讀取資源文件

ContextLoader162行指向了ContextLoader.properties,一個配置文件,它指向了org.springframework.web.context.support.XmlWebApplicationContext這個類

private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";

在XmlWebApplicationContext loadBeanDefinitions()獲取到了contextConfigLocation,XmlBeanDefinitionReader使用xsd讀取xml文件,不再詳述,園友可以點進去看看

 

順着剛才的思路,我們看看DispatcherServlet,DispatcherServlet重寫了父類FrameworkServlet的onRefresh(ApplicationContext contex)方法

 

總結下HttpServletBean,FrameworkServlet和DispatcherServlet初始化過程

1.HttpServletBean

初始化web.xml中的參數

2.FrameworkServlet

將上下文賦予當前Servlet

3.DispatcherServlet

初始化HandlerMapping(請求映射),HandlerExceptionResolver(異常處理),ViewResolver(視圖解析)等功能實現類

 

 

3.DispatcherServlet處理請求

在瀏覽器輸入http://localhost:8080/springmvcdemo/employee,觸發DispatcherServlet的processRequest方法

我不得不說下其父類FrameworkServlet的processRequest方法

previousLocaleContext獲取和當前線程相關的LocaleContext

根據已有請求構造一個新的和當前線程相關的LocaleContext

previousAttributes獲取和當前線程綁定的RequestAttributes

為已有請求構造新的ServletRequestAttributes,加入預綁定屬性

initContextHolders讓新構造的RequestAttributes和ServletRequestAttributes和當前線程綁定,加入到ThreadLocal,完成綁定

抽象方法doService由FrameworkServlet子類DispatcherServlet重寫

resetContextHolders方法解除RequestAttributes,ServletRequestAttributes和當前線程的綁定

注冊監聽事件ServletRequestHandledEvent,在調用上下文的時候產生Event

 

現在我們看下DispatcherServlet的doService方法

attributesSnapshot用來保存request域中的數據,可以叫做“快照”

進入doDispatch方法

 

接下來我們看一看doDispatch方法,內容很多,我在這做些簡述,細節部分后續會逐一分析

932行checkMultipart方法將request轉化成Multipart request

936行HandlerExecutionChain獲取Handler,有攔截器、Bean、BeanFactory,並對應上請求的Controller和Service等等

943行HandlerAdapter獲取到各種argumentResolvers,用來解析參數,還能獲取到各種returnValueHandlers,用來處理類返回值(后續會詳解)

963行通過HandlerAdapter handle方法返回視圖模型ModelAndView

969行給ModelAndView設置viewName

970行使用applyPostHandle方法攔給已注冊的攔截器放行,我們此時並沒有聲明攔截器,spring給我們默認生成兩個默認已注冊的攔截器,如下

結束,文中難免有錯誤,希望園友能及時指出

3.參考

https://docs.spring.io/spring/docs/4.3.7.RELEASE/spring-framework-reference/htmlsingle/#mvc-servlet

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM