SpringMVC原始碼--DispatcherServlet的載入和初始化
之前的文章介紹了Spring的初始化過程,Spring Web應用初始化依賴於Web容器的初始化,這在之前已經提到過了。那麼初始化完成後,SpringMVC又是怎樣發揮作用的呢?首先呢,Web容器初始化完成後會繼續讀取web.xml裡的節點,我們知道DispatcherServlet是配置在web.xml中的,所以DispatcherServlet是由Web容器主動去載入的。那麼,DispatcherServlet到底是個什麼呢?
首先,DispatcherServlet仍是一個Servlet容器。在Web容器初始化的過程中,會建立每個Servlet容器的的例項物件。我們都知道,在Tomcat容器中,維護了一個執行緒池,每當有一個客戶請求抵達伺服器的時候,就分配一個執行緒來處理該請求。同時,Servlet是單例多執行緒的,也就是多個請求共用一個Servlet容器。所以呢,DispatcherServlet的
按照慣例,我們需要先找到DispatcherServlet的初始化入口,開啟原始碼,它的繼承關係如下:
可以看到,DispatcherServlet擁有一系列的父類,頂級父類是GenericServlet,它是Servlet介面的最簡單實現。Web容器會直接呼叫DispatcherServlet擁有的init()方法,其實現在HttpServletBean類中,如下:
public final void init() throws ServletException {
// Set bean properties from init parameters. 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()方法的呼叫,它的具體實現在FrameworkServlet類中。 protected final void initServletBean() throws ServletException { this.webApplicationContext = initWebApplicationContext(); initFrameworkServlet(); }
protected WebApplicationContext initWebApplicationContext() {
//ROOT上下文(ContextLoaderListener方式載入的) WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); WebApplicationContext wac = null;
if (this.webApplicationContext != null) { wac = this.webApplicationContext;
// 1、在建立該Servlet注入的上下文 if (wac instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac; if (!cwac.isActive()) { if (cwac.getParent() == null) { cwac.setParent(rootContext); } configureAndRefreshWebApplicationContext(cwac); } } } if (wac == null) {
//2、查詢已經繫結的上下文 wac = findWebApplicationContext(); } if (wac == null) {
//3、如果沒有找到相應的上下文,並指定父親為ContextLoaderListener wac = createWebApplicationContext(rootContext); }
if (!this.refreshEventReceived) {
//4、重新整理上下文(執行一些初始化)onRefresh(wac); }
if (this.publishContext) { String attrName = getServletContextAttributeName(); getServletContext().setAttribute(attrName, wac); }
return wac; }
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) { if (ObjectUtils.identityToString(wac).equals(wac.getId())) { // The application context id is still set to its original default value // -> assign a more useful id based on available information if (this.contextId != null) { wac.setId(this.contextId); } else { // Generate default id... wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(getServletContext().getContextPath()) + "/" + getServletName()); } }
wac.setServletContext(getServletContext()); wac.setServletConfig(getServletConfig()); wac.setNamespace(getNamespace()); wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
ConfigurableEnvironment env = wac.getEnvironment(); if (env instanceof ConfigurableWebEnvironment) { ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig()); }
postProcessWebApplicationContext(wac); applyInitializers(wac); wac.refresh(); }
initWebApplicationContext()方法不用多說,獲取通過ContextLoader初始化完成的Spring容器的上下文,然後以此為父容器來載入DispatcherServlet。如果獲取失敗,則分情況間接建立一個上下文。追蹤程式碼可以發現最後都是間接呼叫了configureAndRefreshWebApplicationContext()方法來重新整理容器。重新整理容器可以類比Spring容器的啟動過程,實現過程在AbstractApplicationContext類中。容器重新整理工作後就準備完成了,通過onRefresh()方法來做一些初始化工作,它的實現在DispatcherServlet類中,如下:
protected void onRefresh(ApplicationContext context) { initStrategies(context); }
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
//檔案上傳解析,如果請求型別是multipart將通過MultipartResolver進行檔案上傳解析
initLocaleResolver(context);
//本地化解析
initThemeResolver(context);
//主題解析
initHandlerMappings(context);
//通過HandlerMapping,將請求對映到處理器
initHandlerAdapters(context);
//通過HandlerAdapter支援多種型別的處理器
initHandlerExceptionResolvers(context);
//如果執行過程中遇到異常,將交給HandlerExceptionResolver來解析
initRequestToViewNameTranslator(context);
//直接解析請求到檢視名
initViewResolvers(context);
//通過viewResolver解析邏輯檢視到具體檢視實現
initFlashMapManager(context);
//flash對映管理器
}
以上,就是所有內容了,可以看出這是一種很巧妙的設計。