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到底做了什麼
Servlet ConfigPropertyValues是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,FrameworkServletBean和DispatcherServlet的作用
1.HttpServletBean
初始化web.xml中的引數
2.FrameworkServlet
將上下文賦予當前Servlet
3.DispatcherServlet
初始化HandlerMapping(請求對映),HandlerExceptionResolver(異常處理),ViewResolver(檢視解析)等功能實現類
3.DispatcherServlet處理請求
我不得不說下其父類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