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