我舉得這篇文章解決了個人不少疑惑,理清了我之前不太清楚的Context關係,讀懂這篇文章頗有助於理解源碼,html
原文連接在這裏:https://www.jianshu.com/p/2537e2fec546前端
我把它轉載在本身博客裏,懼怕之後找不到,原文以下java
網上博客中看到一句話,很形容的描繪了web程序和上下文的關係,這裏引用一下來講明:若是對「上下文」不太瞭解的,我這邊說下,程序裏面所謂的「上下文」就是程序的執行環境,打個比方:你有家吧?若是家都沒有就別學編程了,租的也行啊!你就至關於web程序,家就至關於web程序的上下文,你能夠在家裏放東西,也能夠取東西,你的衣食住行都依賴這個家,這個家就是你生活的上下文環境。web
該博客地址: Spring和SpringMVC配置中父子WebApplicationContext的關係spring
首先,對於一個web應用,其部署在web容器中,web容器提供其一個全局的上下文環境,這個上下文就是ServletContext,其爲後面的spring IoC容器提供宿主環境;編程
其次,在web.xml中會提供有contextLoaderListener。在web容器啓動時,會觸發容器初始化事件,此時contextLoaderListener會監聽到這個事件,其contextInitialized方法會被調用,在這個方法中,spring會初始化一個啓動上下文,這個上下文被稱爲根上下文,即WebApplicationContext,這是一個接口類,確切的說,其實際的實現類是XmlWebApplicationContext。這個就是spring的IoC容器,其對應的Bean定義的配置由web.xml中的context-param標籤指定。在這個IoC容器初始化完畢後,spring以【WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE】爲屬性Key,將其存儲到ServletContext中,便於獲取;tomcat
再次,contextLoaderListener監聽器初始化完畢後,開始初始化web.xml中配置的Servlet,這個servlet能夠配置多個,以最多見的DispatcherServlet爲例,這個servlet其實是一個標準的前端控制器,用以轉發、匹配、處理每一個servlet請求。DispatcherServlet上下文在初始化的時候會創建本身的IoC上下文,用以持有spring mvc相關的bean。在創建DispatcherServlet本身的IoC上下文時,會利用【WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE】先從ServletContext中獲取以前的根上下文(即WebApplicationContext)做爲本身上下文的parent上下文(有個parent屬性做爲對Spring的ApplicationContext的引用)。有了這個parent上下文以後,再初始化本身持有的上下文。這個DispatcherServlet初始化本身上下文的工做在其initStrategies方法中能夠看到,大概的工做就是初始化處理器映射、視圖解析等。這個servlet本身持有的上下文默認實現類也是XmlWebApplicationContext。初始化完畢後,spring以與servlet的名字相關(此處不是簡單的以servlet名爲Key,而是經過一些轉換,具體可自行查看源碼)的屬性爲屬性Key,也將其存到ServletContext中,以便後續使用。這樣每一個servlet就持有本身的上下文,即擁有本身獨立的bean空間,同時各個servlet共享相同的bean,即根上下文(第2步中初始化的上下文)定義的那些bean。mvc
經過上面這個Spring的啓動過程,咱們能夠清楚的瞭解ServletContext、WebApplicationContext、XmlWebApplicationContext、以及DispatcherServlet上下文之間的關係,而且會將WebApplicationContext放在ServletContext中。app
首先說說ServletContext這個web應用級的上下文。web容器(好比tomcat、jboss、weblogic等)啓動的時候,它會爲每一個web應用程序建立一個ServletContext對象 它表明當前web應用的上下文(注意:是每一個web應用有且僅建立一個ServletContext,一個web應用,就是你一個web工程)。一個web中的全部servlet共享一個ServletContext對象,因此能夠經過ServletContext對象來實現Servlet之間的通信。在一個繼承自HttpServlet對象的類中,能夠經過this.getServletContext來獲取。async
經過源碼詳細說明一下 第二步 的過程,web.xml(上圖)中咱們配置了ContextLoaderListener,該listener實現了ServletContextListener的contextInitialized方法用來監聽Servlet初始化事件:
<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/SpringApplicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
由下面的源碼能夠發現初始化的是WebApplicationContext的IoC容器,它是一個接口類,其默認實現是XmlWebApplicationContext。
public class ContextLoaderListener extends ContextLoader implements ServletContextListener { public ContextLoaderListener() { } public ContextLoaderListener(WebApplicationContext context) { super(context); } @Override public void contextInitialized(ServletContextEvent event) { initWebApplicationContext(event.getServletContext()); } @Override public void contextDestroyed(ServletContextEvent event) { closeWebApplicationContext(event.getServletContext()); ContextCleanupListener.cleanupAttributes(event.getServletContext()); } }
這是ContextLoaderListener中的contextInitialized()方法,這裏主要是用initWebApplicationContext()方法來初始化WebApplicationContext。這裏涉及到一個經常使用類WebApplicationContext:它繼承自ApplicationContext,在ApplicationContext的基礎上又追加了一些特定於Web的操做及屬性。
initWebApplicationContext(event.getServletContext()),進行了建立根上下文,並將該上下文以key-value的方式存儲到ServletContext中。
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) { 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 { // Store context in local instance variable, to guarantee that // it is available on ServletContext shutdown. if (this.context == null) { this.context = createWebApplicationContext(servletContext); } if (this.context instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context; if (!cwac.isActive()) { // The context has not yet been refreshed -> provide services such as // setting the parent context, setting the application context id, etc if (cwac.getParent() == null) { // The context instance was injected without an explicit parent -> // determine parent for root web application context, if any. ApplicationContext parent = loadParentContext(servletContext); cwac.setParent(parent); } configureAndRefreshWebApplicationContext(cwac, servletContext); } } 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); } if (logger.isDebugEnabled()) { logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" + WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]"); } if (logger.isInfoEnabled()) { long elapsedTime = System.currentTimeMillis() - startTime; logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms"); } 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; } }
在initWebApplicationContext()方法中主要體現了WebApplicationContext實例的建立過程。首先,驗證WebApplicationContext的存在性,經過查看ServletContext實例中是否有對應key的屬性驗證WebApplicationContext是否已經建立過實例。若是沒有經過,createWebApplicationContext()方法來建立實例,並存放至ServletContext中。
protected WebApplicationContext createWebApplicationContext(ServletContext sc) { Class<?> contextClass = determineContextClass(sc); if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]"); } return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); }
在createWebApplicationContext()方法中,經過BeanUtils.instanceClass()方法建立實例,而WebApplicationContext的實現類名稱則經過determineContextClass()方法得到。
protected Class<?> determineContextClass(ServletContext servletContext) { String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM); if (contextClassName != null) { try { return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader()); } catch (ClassNotFoundException ex) { throw new ApplicationContextException( "Failed to load custom context class [" + contextClassName + "]", ex); } } else { contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName()); try { return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader()); } catch (ClassNotFoundException ex) { throw new ApplicationContextException( "Failed to load default context class [" + contextClassName + "]", ex); } } }
determineContextClass()方法,經過defaultStrategies.getProperty()方法得到實現類的名稱,而defaultStrategies是在ContextLoader類的靜態代碼塊中賦值的。具體的途徑,則是讀取ContextLoader類的同目錄下的ContextLoader.properties屬性文件來肯定的。
也就是說,在初始化的過程當中,程序會首先讀取ContextLoader類的同目錄下的屬性文件ContextLoader.properties,並根據其中的配置提取將要實現WebApplicationContext接口的實現類,並根據這個類經過反射進行實例的建立。
綜上所述:
LoaderListener監聽器的做用就是啓動Web容器時,自動裝配ApplicationContext的配置信息。由於它實現了ServletContextListener這個接口,在web.xml配置了這個監聽器,啓動容器時,就會默認執行它實現的contextInitialized()方法初始化WebApplicationContext實例,並放入到ServletContext中。因爲在ContextLoaderListener中關聯了ContextLoader這個類,因此整個加載配置過程由ContextLoader來完成。
protected void initStrategies(ApplicationContext context) { initMultipartResolver(context); initLocaleResolver(context); initThemeResolver(context); initHandlerMappings(context); initHandlerAdapters(context); initHandlerExceptionResolvers(context); initRequestToViewNameTranslator(context); initViewResolvers(context); initFlashMapManager(context); }
須要作的八件事情以下所述:
經過查看DispatcherServlet(源碼內容太多就不往上放了),DispatcherServlet繼承自FrameworkServlet,而FrameworkServlet是繼承自HttpServletBean的,HttpServletBean又繼承了HttpServlet。這是由於DispatcherServlet自己就得是一個Servlet,且含有doGet()和doPost()方法,Web容器才能夠調用它,因此它的頂級父類爲含有這倆方法的HttpServlet。具體的Web請求,會通過FrameServlet的processRequest方法簡單處理後,緊接着調用DispatcherServlet的doService方法,而在這個方法中封裝了最終調用處理器的方法doDispatch。這也意味着,DispatcherServlet的最主要的核心功能由doService和doDispatch實現的。
DispatcherServlet類的方法大體可分爲三種:
- 初始化相關處理類的方法。
- 響應Http請求的方法。
- 執行處理請求邏輯的方法。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { try { ModelAndView mv = null; Object dispatchException = null; try { processedRequest = this.checkMultipart(request); multipartRequestParsed = processedRequest != request; mappedHandler = this.getHandler(processedRequest); if (mappedHandler == null || mappedHandler.getHandler() == null) { this.noHandlerFound(processedRequest, response); return; } HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler()); String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (this.logger.isDebugEnabled()) { this.logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified); } if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) { return; } } if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } this.applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception var20) { dispatchException = var20; } catch (Throwable var21) { dispatchException = new NestedServletException("Handler dispatch failed", var21); } this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException); } catch (Exception var22) { this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22); } catch (Throwable var23) { this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23)); } } finally { if (asyncManager.isConcurrentHandlingStarted()) { if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else if (multipartRequestParsed) { this.cleanupMultipart(processedRequest); } } }
protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception{ //遍歷model裏面的數據,填充到request域 for (Map.Entry<String, Object> entry : model.entrySet()){ String modeName = entry.getKey(); Object modelValue = entry.getValue(); if(modelValue != null){ request.setAttribute(modelName, modelValue); if(logger.isDebugEnabled()){ logger.debug("Added model object '" + modelName + "' of type [" + modelValue.getClass.getName() +"] to request in view with name '" + getBeanName() + "'"); } else{ request.removeAttribute(modelName); if(logger.isDebugEnabled()){ logger.debug("Remove modek object '" +modelName + "' from request in view with name '" +getBeanName() + "'"); } } } } }
能夠看到,在這裏會把ModelAndView中model的數據遍歷出來,分爲key和value,而且將數據設置在request的attribute域中。以後加載頁面時就可使用標籤在request域中獲取返回參數了。