作為一個javaer,Spring、SpringMVC目前是一項必不可少的技能了。SpringMVC在struts2之後異軍突起,現如今已經完全超過了struts2,成為了javaweb開發的主流框架。今天我們基於Spring5.0對SpringMVC容器初始化過程、DispatcherServlet初始話過程、DispatcherServlet處理請求流程三個方面進行分析。
我們可以看到WebApplicationContext繼承自ApplicationContext,也就是我們web開發中的IOC容器,我們一般用到的是它的實現類 XmlWebApplicationContext。
開發web的時候,我們都需要在web.xml中配置Spring的監聽器、Spring配置文件
<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-context.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
我們從ContextLoaderListener入手,講解WebApplicationContext的創建流程。
ContextLoaderListener繼承了ContextLoader,同時實現了ServletContextListener介面,通過實現contextInitialized來初始化IOC容器,而主要的方法都在ContextLoader裡面實現。
@Override public void contextInitialized(ServletContextEvent event) { initWebApplicationContext(event.getServletContext()); }
我們來看看initWebApplicationContext方法的具體實現:
//1.先判斷ServletContext中是否已經存在WebApplicationContext,比如我們不小心多配置了一個ContextLoaderListener的時候,就會導致重複的初始化 if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) { throw new IllegalStateException( "Cannot initialize context because there is already a root application context present - " + "check whether you have multiple ContextLoader* definitions in your web.xml!"); }
Log logger = LogFactory.getLog(ContextLoader.class); servletContext.log("Initializing Spring root WebApplicationContext"); if (logger.isInfoEnabled()) { logger.info("Root WebApplicationContext: initialization started"); } long startTime = System.currentTimeMillis();
try { if (this.context == null) { //2.創建WebApplicationContext對象 this.context = createWebApplicationContext(servletContext); } if (this.context instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context; if (!cwac.isActive()) { if (cwac.getParent() == null) { ApplicationContext parent = loadParentContext(servletContext); cwac.setParent(parent); } configureAndRefreshWebApplicationContext(cwac, servletContext); } } //3.把創建好的IOC容器放到Serverlet容器中 servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader(); if (ccl == ContextLoader.class.getClassLoader()) { currentContext = this.context; } else if (ccl != null) { currentContextPerThread.put(ccl, this.context); }
return this.context; } catch (RuntimeException ex) { logger.error("Context initialization failed", ex); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex); throw ex; } catch (Error err) { logger.error("Context initialization failed", err); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err); throw err; }
我們這裡就不展示過多的源碼細節。
圖片地址:https://oscimg.oschina.net/oscnet/8e97e66631ea98f3d756cbb4698d2b6a18e.jpg
從上面的圖我們可以知道,WebApplicationContext的初始化,確切的說是XmlWebApplicationContext對象的創建過程主要包括以下幾個步驟:
@Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { //1.準備刷新容器 prepareRefresh(); //2.獲取beanFactory,這一步會調用XmlWebApplication的loadBeanDefinitions()方法,從我們的配置文件載入所有的bean定義 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); //3.準備刷新beanFactory prepareBeanFactory(beanFactory); try { //4.設置beanFactory的後置處理器 postProcessBeanFactory(beanFactory); //5.運行beanFactory的後置處理器 invokeBeanFactoryPostProcessors(beanFactory); //6.註冊bean的後置處理器,這一步主要是獲取4 5兩步中設置的BeanPostProcessor進行分類、排序等處理,以確定BeanPostProcessor的調用順序 registerBeanPostProcessors(beanFactory); //7.初始化MessageSource,用來處理國際化 initMessageSource(); //8.註冊事件廣播器 initApplicationEventMulticaster(); //9.再次初始化其它的bean onRefresh(); //10.註冊監聽器 registerListeners(); //11.完成對BeanFactory的初始化,初始化所有非non-lazy-init的bean,這一步會創建bean實例、運行bean的後置處理器、載入我們配置的properties文件等 finishBeanFactoryInitialization(beanFactory); //12.完成刷新,這裡會發布一個ContextRefreshedEvent的事件 finishRefresh(); } catch (BeansException ex) { destroyBeans(); cancelRefresh(ex); throw ex; } finally { resetCommonCaches(); } }
到這裡,我們WebAp初始化就已經完成了,但是我們不免有個疑問,我們的Controller,HandlerInterceptor這些沒有在我們設置的contextConfigLocation裡面,那他們又是怎麼初始化的呢,下面我們繼續對DispatcherServlet的初始化過程進行分析。
<!-- spring mvc servlet --> <servlet> <servlet-name>mvc-dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet>
<servlet-mapping> <servlet-name>mvc-dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
這裡我們又配置了一個contextConfigLocation,這個就是SpringMVC容器的配置文件,我們一開始也配置了一個contextConfigLocation,他們之間沒有任何的區別,我們第一個配置的contextConfigLocation是作為一個父容器,現在配置的是作為一個子容器,我們只能有一個父容器,但是可以配置多個子容器,也就是我們可以在一個項目中配置多個DispatcherServlet。
[@Override](https://my.oschina.net/u/1162528) public final void init() throws ServletException { if (logger.isDebugEnabled()) { logger.debug("Initializing servlet " + getServletName() + ""); }
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) {
throw ex; } }
//最主要的操作都在這裡面了 initServletBean(); }
從上面的圖我們可以知道,SpringMVC子容器的初始是在DispatcherServlet初始化的時候完成的,主要包括下面幾個方面:
ServletContext#getAttribute()
@Override protected void onRefresh(ApplicationContext context) { initStrategies(context); }
protected void initStrategies(ApplicationContext context) { initMultipartResolver(context); initLocaleResolver(context); initThemeResolver(context); initHandlerMappings(context); initHandlerAdapters(context); initHandlerExceptionResolvers(context); initRequestToViewNameTranslator(context); initViewResolvers(context); initFlashMapManager(context); }
至此,我們已經分析了在web項目中父容器的初始化流程和自容器的初始化流程,下面的內容我們來分析DispatcherServlet處理請求的流程。
我們先回憶下在沒有MVC框架的時候,我們是怎麼處理請求的。在我們還在通過繼承Servlet介面的遠古時代,我們的請求到達Servelt的時候,會調用service()方法,我們通過重寫doPost()、doGet()等方法,自己操作Request、Response處理請求,當然我們需要在web.xml中配置一大堆的servelt。
圖片地址:https://oscimg.oschina.net/oscnet/d4a7cf115775da5aa358b6d5e9b3224d857.jpg
從上面的圖中我們可以看到DispatcherServlet處理請求主要包括以下幾個方面:
至此,我們對SpringMVC WebApplicationContext初始化、DispatcherServlet初始化、DispatcherServlet請求處理流程進行了大概的分析。總體來說,分析的還是比較粗糙,很多細節的方面沒有深入分析,建議大家多去看看源碼內容,會有不一樣的理解和感受。文章中可能有表達不準確、理解不正確的地方,歡迎隨時指正,一起探討。