使用過SpringMVC的都知道DispatcherServlet,下麵介紹下該Servlet的啟動與初始化。作為Servlet,DispatcherServlet的啟動與Serlvet的啟動過程是相聯繫的。在Serlvet的初始化過程程中,Serlvet的init方法會被調用,以進行初始化。Dis ...
使用過SpringMVC的都知道DispatcherServlet,下麵介紹下該Servlet的啟動與初始化。作為Servlet,DispatcherServlet的啟動與Serlvet的啟動過程是相聯繫的。在Serlvet的初始化過程程中,Serlvet的init方法會被調用,以進行初始化。DispatcherServlet的基類HttpServletBean中的這個初始化過程源碼如下:
public final void init() throws ServletException { if (logger.isDebugEnabled()) { logger.debug("Initializing servlet '" + getServletName() + "'"); } // 獲取Servlet的初始化參數,對Bean屬性進行配置 try { PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties); 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) { logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex); throw ex; } // 調用子類的initServletBean方法進行具體的初始化工作 initServletBean(); if (logger.isDebugEnabled()) { logger.debug("Servlet '" + getServletName() + "' configured successfully"); } } // initServletBean這個初始化工作位於FrameworkServlet類中 protected final void initServletBean() throws ServletException { getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'"); if (this.logger.isInfoEnabled()) { this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started"); } long startTime = System.currentTimeMillis(); // 這裡初始化上下文 try { this.webApplicationContext = initWebApplicationContext(); initFrameworkServlet(); } catch (ServletException ex) { this.logger.error("Context initialization failed", ex); throw ex; } catch (RuntimeException ex) { this.logger.error("Context initialization failed", ex); throw ex; } if (this.logger.isInfoEnabled()) { long elapsedTime = System.currentTimeMillis() - startTime; this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " + elapsedTime + " ms"); } } protected WebApplicationContext initWebApplicationContext() { // 調用WebApplicationContextUtils來得到根上下文,它保存在ServletContext中 // 使用它作為當前MVC上下文的雙親上下文 WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); WebApplicationContext wac = null; if (this.webApplicationContext != null) { wac = this.webApplicationContext; if (wac instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac; if (!cwac.isActive()) { if (cwac.getParent() == null) { cwac.setParent(rootContext); } configureAndRefreshWebApplicationContext(cwac); } } } if (wac == null) { wac = findWebApplicationContext(); } if (wac == null) { wac = createWebApplicationContext(rootContext); } if (!this.refreshEventReceived) { onRefresh(wac); } // 把當前建立的上下文存到ServletContext中,使用的屬性名是跟當前Servlet名相關的 if (this.publishContext) { String attrName = getServletContextAttributeName(); getServletContext().setAttribute(attrName, wac); if (this.logger.isDebugEnabled()) { this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() + "' as ServletContext attribute with name [" + attrName + "]"); } } return wac; }
在初始化開始時,需要讀取配置在ServletContext中的Bean屬性參數,這些屬性參數設置在web.xml的Web容器初始化參數中。
接著會執行DispatcherServlet持有的IOC容器的初始化過程,在這個過程中,一個新的上下文會被建立起來,這個DispatcherServlet持有的上下文被設置為根上下文的子上下文。可以這麼理解,根上下文是和web應用相對應的一個上下文,而DispatcherServlet持有的上下文是和Servlet對應的一個上下文。在一個web應用中可以容納多個Servlet存在;對應的,對於應用在web容器中的上下文體系,一個根上下文可以作為許多Serlvet上下文的雙親上下文。對這點的理解有助於在web環境中IOC容器的Bean設置和檢索有所幫助,因為在向IOC容器getBean時,IOC容器會先向其雙親上下文去getBean,換句話說就是在根上下文中定義的Bean是可以被各個Servlet持有的上下文得到和共用的。DispatcherServlet持有的上下文被建立後,也需要和其他 IOC容器一樣完成初始化操作,這個初始化操作就是通過refresh方法完成的,最後DispatcherServlet再給自己持有的上下文命名並設置到web視窗的上下文中(即ServletContext),這個名稱和在web.xml中設置的DispatcherServlet的Servlet名稱有關,進而保證這個上下文在web環境上下文體系中的唯一性。
上面代碼執行後,這個Servlet的上下文就建立起來了,具體取得根上下文的過程是在WebApplicationContextUtils中實現的,源碼如下:
public static WebApplicationContext getWebApplicationContext(ServletContext sc) { // ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE這個屬性代表的根上下文 // 在ContextLoaderListener初始化的過程中被建立,並設置到ServletContext中 return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); }
這個根上下文是ContextLoader設置到ServletContext中的,使用屬性ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,同時對這個IOC容器的Bean配置文件,ContextLoader也進行了設置,預設的位置是在/WEB-INF/applicationContext.xml文件中,由於這個根上下文是DispatcherServlet建立的上下文的雙親上下文,所以根上下文中管理的bean是可以被DispatcherServlet的上下文使用的,反過來則不行,通過getBean向IOC容器獲取bean時,會先到其雙親IOC容器嘗試獲取。
回到FrameworkServlet繼續看DispatcherServlet的上下文是怎樣建立的,源碼如下:
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) { Class<?> contextClass = getContextClass(); if (this.logger.isDebugEnabled()) { this.logger.debug("Servlet with name '" + getServletName() + "' will try to create custom WebApplicationContext context of class '" + contextClass.getName() + "'" + ", using parent context [" + parent + "]"); } 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"); } // 實例化需要的具體上下文對象,併為這個上下文對象設置屬性 // 這裡使用的是DEFAULT_CONTEXT_CLASS,這個DEFAULT_CONTEXT_CLASS被設置為XmlWebApplicationContext.class, // 所以在DispatcherServlet中使用的IOC容器是XmlWebApplicationContext ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); wac.setEnvironment(getEnvironment()); // 設置雙親上下文(也就是根上下文) wac.setParent(parent); wac.setConfigLocation(getContextConfigLocation()); configureAndRefreshWebApplicationContext(wac); return wac; }
建立DispatcherServlet的上下文,需要把根上下文作為參數傳遞給它,再使用反射實例化上下文對象,併為它設置參數,按預設配置的話這個上下文對象就是XmlWebApplicationContext對象,這個類型是在DEFAULT_CONTEXT_CLASS參數中設置好並允許BeanUtils使用的,實例化結束之後,還需要為這個上下文對象設置好一些基本的配置,這些配置包括它的雙親上下文、Bean定義配置的文件位置等,完成配置後就通過調用IOC容器的refresh方法來完成IOC容器的最終初始化。
通過上面web容器一系列的操作後,在這個上下文體系建立和初始化完畢的基礎上,Spring MVC就可以發揮作用了。此時DispatcherServlet就持有一個以自己的Servlet名稱命名的IOC容器,這個IOC容器建立後,意味著DispatcherServlet擁有自己的Bean定義空間,這為使用各個獨立的XML文件來配置MVC中各個Bean創建了條件,初始化完成後,Spring MVC的具體實現和普通的Spring應用程式的實現並沒有太多差別,在DispatcherServlet的初始化過程中,以對HandlerMapping的初始化調用作為觸發點,可以看下圖Spring MVC模塊初始化的方法調用關係圖,
這個調用關係最初由HttpServletBean的init方法觸發,這個HttpServletBean是HttpServlet的子類,接著會在HttpServletBean的子類FrameworkServlet中對IOC容器完成初始化,在這個初始化方法中會調用DispatcherServlet的initStrategies方法,在這個initStrategies方法中,啟動整個Spring MVC框架的初始化工作。
從上面的方法調用關係圖也可以看出對MVC的初始化是在DispatcherServlet的initStrategies中完成的,包括對各種MVC框架的實現元素,比如國際化支持LocalResolver、視圖生成的ViewResolver等的初始化工作,源碼如下:
protected void initStrategies(ApplicationContext context) { initMultipartResolver(context); initLocaleResolver(context); initThemeResolver(context); initHandlerMappings(context); initHandlerAdapters(context); initHandlerExceptionResolvers(context); initRequestToViewNameTranslator(context); initViewResolvers(context); initFlashMapManager(context); }
上面各個初始化方法的名稱應該比較好理解,這裡以常見的HandlerMapping為例來說明initHandlerMappings()實現,Mapping映射的作用就是為HTTP請求找到相應的Controller控制器,HandlerMappings完成對MVC中Controller的定義和配置,DispatcherServlet中HandlerMappings初始化過程源碼如下:
private void initHandlerMappings(ApplicationContext context) { this.handlerMappings = null; // 這裡導入所有的HandlerMapping Bean,這些Bean可以在當前的DispatcherServlet的IOC // 容器中,也可能在其雙親上下文中,這個detectAllHandlerMappings的預設值設為true, // 即預設地從所有的IOC容器中獲取 if (this.detectAllHandlerMappings) { Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false); if (!matchingBeans.isEmpty()) { this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values()); OrderComparator.sort(this.handlerMappings); } } else { try { // 可以根據名稱從當前的IOC容器中通過getBean獲取HandlerMapping HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class); this.handlerMappings = Collections.singletonList(hm); } catch (NoSuchBeanDefinitionException ex) { } } // 如果沒找到HandlerMappings,那麼需要為Servlet設置預設的HandlerMappings, // 這些預設的值可以設置在DispatcherServlet.properties中 if (this.handlerMappings == null) { this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class); if (logger.isDebugEnabled()) { logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default"); } } }
在HandlerMapping初始化的過程中,把在Bean配置文件中配置好的handlerMapping從IOC容器中取得。經過上面的讀取過程,handlerMappings變數就已經獲取了在BeanDefinition中配置好的映射關係。其他的初始化過程和handlerMappings比較類似,都是從IOC容器中讀入配置,所以說MVC初始化過程是建立在IOC容器已經初始化完成的基礎之上的。執行完其他的各個初始化操作後,整個初始化過程就基本完成了。