本文中使用的Spring框架的版本爲5.1前端
從設計上說,Spring Web MVC 使用前端控制器模式圍繞一箇中心Servlet
進行設計,這個中心Servlet
就是DispatcherServlet
,在DispatcherServlet
中提供了用於處理請求的通用邏輯,而具體工做委託給可配置的組件執行,經過這種模式使得Spring Web MVC框架變得很是靈活。java
與其它的Servlet
同樣,DispatcherServlet
須要根據Servlet規範使用Java代碼或者在web.xml
文件中進行配置。web
Spring Web MVC在啓動時,最早加載DispatcherServlet
,而後DispatcherServlet
再根據配置加載請求映射、視圖解析、異常處理所需的組件。spring
下面是在web.xml
文件中對DispatcherServlet
進行配置的示例:app
<web-app>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/app-context.xml</param-value>
</context-param>
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
</web-app>
複製代碼
在對請求進行處理時,DispatcherServlet
會把委託下面這些Bean對請求進行處理,以及給出適當的響應結果。框架
HandlerMappingide
將請求與handler
進行映射,HandlerMapping
有兩個主要的實現:函數
RequestMappingHandlerMapping
SimpleUrlHandlerMapping
前者用於對@RequestMapping
註解提供支持,後者支持控制器的顯示註冊。源碼分析
HandlerAdapter佈局
幫助DispatcherServlet
調用與請求路徑匹配的handler
去處理請求。
經過使用適配器的方式使DispatcherServlet
不用關心handler
的具體實現細節,好比:調用@Controller註解的控制器須要對該註解進行處理。
HandlerExceptionResolver
使用不一樣的策略對異常進行處理,好比:返回的HTTP狀態碼是5xx仍是4xx。
ViewResolver
對視圖進行解析,好比對JSP與FreeMarker的模版文件進行解析。
LocaleResolver, LocaleContextResolver
提供本地化支持,好比:多國語言、時區等。
ThemeResolver
提供主題支持,用於個性化佈局。
MultipartResolver
用於解析multi-part
請求。
FlashMapManager
用於管理存放Falsh Attribute
的FlashMap
,Falsh Attribute
用於跨請求傳遞數據。
從源碼中能夠找到DispatcherServlet
類的定義以下:
public class DispatcherServlet extends FrameworkServlet 複製代碼
能夠看出,它繼承自類FrameworkServlet
,下面咱們再來看一下類的總體繼承關係:
從類的繼承關係來看,最後會經過實現Servlet
接口來處理HTTP請求,其中與Servlet
有關係的類按繼承順序從上至下分別是:
Servlet
接口。GenericServlet
。HttpServlet
。HttpServlet
。FrameworkServlet
。其中GenericServlet
與HttpServlet
類只是簡單對Servlet
接口作了一些封裝與擴展,所以能夠把分析的重點放在HttpSerlvetBean
、FrameworkServlet
與DispatcherServlet
這三個類上面。
在DispatcherServlet
初始化時,這三個類之間調用順序以下圖所示:
根據Servlet
規範,在Servlet
接口中會存在一個init()
方法,在一個Servlet
實被例化以後容器將會調用一次該方法。Spring Web MVC經過在HttpSerlvetBean
類中覆寫init()
方法從而實現整個框架的加載。
在HttpSerlvetBean
類的init()
方法中主要作了兩件事,一是從web.xml文件中讀取初始化參數,好比:contextConfigLocation
參數。二是調用由子類實現的initServletBean()
方法完成具體的初始化工做。
init()
方法的實現以下:
@Override
public final void init() throws ServletException {
// 從web.xml文件中讀取初始化參數
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
// 調用由子類實現的`initServletBean()`方法。
initServletBean();
}
複製代碼
在上面這段代碼中有點意思的是從web.xml
文件中讀取初始化參數的方式,在這裏咱們拿contextConfigLocation
參數來舉例子。
對於參數contextConfigLocation
而言,在HttpServletBean
的子類FrameworkServlet
中存在一個具備如下定義的私有變量:
@Nullable
private String contextConfigLocation;
複製代碼
以及對應的Get與Set方法:
public void setContextConfigLocation(@Nullable String contextConfigLocation) {
this.contextConfigLocation = contextConfigLocation;
}
@Nullable
public String getContextConfigLocation() {
return this.contextConfigLocation;
}
複製代碼
可是Spring並不會直接調用setContextConfigLocation()
方法來給contextConfigLocation
變量賦值,而是由BeanWrapper
搭配ResourceEditor
來給變量賦值。
init()
方法在讀取初始化參數以後,便會調用initServletBean()
方法來作初始化工做,該方法的在HttpServletBean
中是一個被protected
修飾的空方法,其定義以下:
protected void initServletBean() throws ServletException 複製代碼
而具體的初始化工做則在HttpServletBean
的子類FrameworkServlet
中經過覆寫initServletBean()
方法來完成。
initServletBean()
方法的實現以下:
protected final void initServletBean() throws ServletException {
try {
// 初始化WebApplicationContext
this.webApplicationContext = initWebApplicationContext();
// 一個Hook,讓子類有機會在上下文初始化後作一些相關的工做
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
}
複製代碼
在該方法中主要作了下面兩件事:
initWebApplicationContext()
方法初始化webApplicationContext
。initFrameworkServlet()
方法讓子類在初始化以後有機會作一些額外的工做。事實上,目前initFrameworkServlet()
是一個沒有使用的空函數,而且子類也沒有對它進行覆寫,因此咱們只須要關注initWebApplicationContext()
方法便可。
initWebApplicationContext()
方法的實現以下:
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// 實例化時若是提供了webApplicationContext參數,則使用它。
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// 若是該上下文尚未進行過refresh則爲它設置一個ID並進行refresh
if (cwac.getParent() == null) {
// 若是該的上下文沒有父上下文則爲它設置一個。
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// 若是實例化時沒有提供上下文,則查找ServletContext中有沒有提供。
wac = findWebApplicationContext();
}
if (wac == null) {
// 若是上面沒找到一個已經存在的上下文,則本身建立一個
wac = createWebApplicationContext(rootContext);
}
// 若是onRefresh尚未被調用,則手動調用一次
if (!this.refreshEventReceived) {
synchronized (this.onRefreshMonitor) {
// 調用子類實現的onRefresh初始化策略對象
onRefresh(wac);
}
}
if (this.publishContext) {
// 將WebApplicationContext放入ServletContext之中
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
複製代碼
這個方法中主要作了下面這些事:
第一,檢查是否是經過構造函數FrameworkServlet(WebApplicationContext webApplicationContext)
傳入了webApplicationContext
參數。若是是,則會對傳入的webApplicationContext
進行一些配置,好比:設置父上下文與上下文ID。
第二,檢查ServletContext中有沒有提供webApplicationContext
。若是有,拿過來直接使用。
第三,若是在第一步與第二步中都沒有發現可用的webApplicationContext
,那就調用createWebApplicationContext()
方法本身建立一個。
第四,作一次兜底,經過refreshEventReceived
變量的值判斷是否調用過onRefresh()
方法,若是從未調用過,則觸發一次調用。
onRefresh
方法除了在initWebApplicationContext()
方法中調用了以外,在FrameworkServlet
中onApplicationEvent()
方法中也調用了onRefresh
方法。
onApplicationEvent()
方法的實現以下:
public void onApplicationEvent(ContextRefreshedEvent event) {
this.refreshEventReceived = true;
synchronized (this.onRefreshMonitor) {
onRefresh(event.getApplicationContext());
}
}
複製代碼
在上面的代碼中除了調用onRefresh()
方法以外,還給變量refreshEventReceived
賦值爲真,確保onRefresh()
方法只會被調用一次。
那麼問題來了,又是誰在調用onApplicationEvent()
方法?
對於這個問題咱們先來看一下用於建立webApplicationContext
的createWebApplicationContext()
方法的實現:
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
Class<?> contextClass = getContextClass();
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
wac.setParent(parent);
// 使用configLocation所指定的配置文件
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
// 對webApplicationContext進行配置
configureAndRefreshWebApplicationContext(wac);
return wac;
}
複製代碼
在這個方法中,主要作了兩件事:一是使用configLocation
所指定的配置文件來加載Bean,二是調用configureAndRefreshWebApplicationContext()
方法對webApplicationContext
進行配置。
若是你還有印象的話,你可能記得下面這段位於initWebApplicationContext()
方法中的代碼也調用過 configureAndRefreshWebApplicationContext()
方法:
if (this.webApplicationContext != null) {
// 實例化時若是提供了webApplicationContext參數,則使用它。
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// 若是該上下文尚未進行過refresh則爲它設置一個ID並進行refresh
if (cwac.getParent() == null) {
// 若是該的上下文沒有父上下文則爲它設置一個。
cwac.setParent(rootContext);
}
// 對webApplicationContext進行配置
configureAndRefreshWebApplicationContext(cwac);
}
}
}
複製代碼
因而乎,咱們要研究一下在configureAndRefreshWebApplicationContext()
方法中作了哪些鮮爲人知的事情。
configureAndRefreshWebApplicationContext()
方法的實現以下:
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// 若是上下文的ID原裝的,則爲其設置一個更具可讀性的ID
// 使用配置文件指定的ID
if (this.contextId != null) {
wac.setId(this.contextId);
}
else {
// 生成一個默認的ID
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
}
}
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
// 添加一個Listener用於監聽webApplicationContext的refresh事件
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
// 初始化PropertySource
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}
postProcessWebApplicationContext(wac);
// 調用ApplicationContextInitializer
applyInitializers(wac);
// 調用webApplicationContext的refresh方法
wac.refresh();
}
複製代碼
在上面的代碼中作了這麼幾件事:
在上面第二步中,ContextRefreshListener
類的實現以下:
private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
FrameworkServlet.this.onApplicationEvent(event);
}
}
複製代碼
經過這個類的實現能夠看出,這個類做爲FrameworkServlet
的內部類在收到上下文已刷新的事件後會調用onApplicationEvent()
方法。
如今咱們已經能夠前面的問題了:是誰在調用onApplicationEvent()
方法?。
經過上述的分析咱們能夠知道在configureAndRefreshWebApplicationContext()
方法中對webApplicationContext
設置了一個監聽器,這個監聽器在監聽到上下文refresh以後會調用onApplicationEvent()
方法。
既然onApplicationEvent()
方法與initWebApplicationContext()
都會調用onRefresh()
方法,那麼在onRefresh()
方法中又作了哪些事情?
onRefresh()
方法在FrameworkServlet
中的定義以下:
protected void onRefresh(ApplicationContext context) {
}
複製代碼
可見,它在FrameworkServlet
類中是一個空方法。具體邏輯由FrameworkServlet
的子類DispatcherServlet
覆寫這個方法來實現,下文將對它的具體實現進行分析。
DispatcherServlet
類中onRefresh()
方法的實現以下:
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
// 初始化解析multi-part請求須要的組件
initMultipartResolver(context);
// 初始化用於提供本地化支持的主鍵
initLocaleResolver(context);
// 初始化用於提供主題支持的組件
initThemeResolver(context);
// 初始化用於請求映射的組件
initHandlerMappings(context);
// 初始化用於對handler進行適配的組件
initHandlerAdapters(context);
// 初始化用於異常處理的組件
initHandlerExceptionResolvers(context);
// 初始化用於視圖解析的組件
initRequestToViewNameTranslator(context);
initViewResolvers(context);
// 初始化用於管理Flash Attribute的組件
initFlashMapManager(context);
}
複製代碼
經過上面的代碼中能夠看出,onRefresh()
方法將具體的工做委託給了initStrategies()
方法。
在initStrategies()
方法中,初始化了一系列的策略對象用於對不一樣的功能提供支持,好比:請求映射、視圖解析、異常處理等。
至此,DispatcherServlet
初始化完畢。
未完,待續。