我舉得這篇文章解決了我的很多疑惑,理清了我以前不太清楚的Context關系,讀懂這篇文章很有助於理解源碼,
原文鏈接在這里:https://www.jianshu.com/p/2537e2fec546
我把它轉載在自己博客里,害怕以后找不到,原文如下
網上博客中看到一句話,很形容的描繪了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中,便於獲取;
第三步:
再次,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。
通過上面這個Spring的啟動過程,我們可以清楚的了解ServletContext、WebApplicationContext、XmlWebApplicationContext、以及DispatcherServlet上下文之間的關系,並且會將WebApplicationContext放在ServletContext中。

1. ServletContext:
首先說說ServletContext這個web應用級的上下文。web容器(比如tomcat、jboss、weblogic等)啟動的時候,它會為每個web應用程序創建一個ServletContext對象 它代表當前web應用的上下文(注意:是每個web應用有且僅創建一個ServletContext,一個web應用,就是你一個web工程)。一個web中的所有servlet共享一個ServletContext對象,所以可以通過ServletContext對象來實現Servlet之間的通訊。在一個繼承自HttpServlet對象的類中,可以通過this.getServletContext來獲取。
2. WebApplicationContext:
通過源碼詳細說明一下 第二步 的過程,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來完成。
3. DispatcherServlet

詳解 第三步,contextLoaderListener監聽器初始化完畢后,開始初始化DispatcherServlet,下面為初始化方法的源碼:
protected void initStrategies(ApplicationContext context) { initMultipartResolver(context); initLocaleResolver(context); initThemeResolver(context); initHandlerMappings(context); initHandlerAdapters(context); initHandlerExceptionResolvers(context); initRequestToViewNameTranslator(context); initViewResolvers(context); initFlashMapManager(context); }
需要做的八件事情如下所述:
- initMultipartResolver:初始化MultipartResolver,用於處理文件上傳服務,如果有文件上傳,那么就會將當前的HttpServletRequest包裝成DefaultMultipartHttpServletRequest,並且將每個上傳的內容封裝成CommonsMultipartFile對象。需要在dispatcherServlet-servlet.xml中配置文件上傳解析器。
- initLocaleResolver:用於處理應用的國際化問題,本地化解析策略。
- initThemeResolver:用於定義一個主題。
- initHandlerMapping:用於定義請求映射關系。
- initHandlerAdapters:用於根據Handler的類型定義不同的處理規則。
- initHandlerExceptionResolvers:當Handler處理出錯后,會通過此將錯誤日志記錄在log文件中,默認實現類是SimpleMappingExceptionResolver。
- initRequestToViewNameTranslators:將指定的ViewName按照定義的RequestToViewNameTranslators替換成想要的格式。
- initViewResolvers:用於將View解析成頁面。
- initFlashMapManager:用於生成FlashMap管理器。
通過查看DispatcherServlet(源碼內容太多就不往上放了),DispatcherServlet繼承自FrameworkServlet,而FrameworkServlet是繼承自HttpServletBean的,HttpServletBean又繼承了HttpServlet。這是因為DispatcherServlet本身就得是一個Servlet,且含有doGet()和doPost()方法,Web容器才可以調用它,所以它的頂級父類為含有這倆方法的HttpServlet。具體的Web請求,會經過FrameServlet的processRequest方法簡單處理后,緊接着調用DispatcherServlet的doService方法,而在這個方法中封裝了最終調用處理器的方法doDispatch。這也意味着,DispatcherServlet的最主要的核心功能由doService和doDispatch實現的。
DispatcherServlet類的方法大致可分為三種:
- 初始化相關處理類的方法。
- 響應Http請求的方法。
- 執行處理請求邏輯的方法。
核心方法 doDispatch()
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); } } }
DispatcherServlet處理該類請求的步驟:
- 在接收到請求后,通過幾級Servlet類型的父類的處理,先調用doService在request中設置一些必需的參數。最終會調用DispatcherServlet的doDispatch方法。
- 在doDispatch方法中,首先檢測request是否包含多媒體類型(如File文件上傳),然后將檢測后的request轉換為processedRequest對象。之后檢測processedRequest對象是否為原始request(如果是,即原來的request不包含多媒體信息),然后將boolean結果賦給multipartRequestParsed變量(若multipartRequestParsed為true,在最后會清除processedRequest對象中的多媒體信息)。
- 十分重要的一步,就是通過調用處理器映射器查找Handler。調用getHandler來獲取相關的處理器對象。在getHandler方法中,利用處理器映射器HandlerMapping通過request來獲取一個包含Handler處理器本身和其前后攔截器interceptor的處理器執行鏈HandlerExecutionChain對象。
- 通過HandlerExecutionChain對象獲取具體的Handler處理器對象,此時使用getHandlerAdapter方法獲取可以處理類型的處理器適配器HandlerAdapter對象。
- 調用HandlerAdapter對象的handle方法,將可能帶有多媒體信息的processRequest對象,原始request對象,以及Handler處理器本身作為參數傳入,handle方法會根據這些參數去執行開發者自己開發的Handler的相關
請求處理邏輯,並返回含有反饋信息和結果視圖信息的ModelAndView對象。 - 獲得ModelAndView對象后,會進行視圖渲染,將model數據填充到request域。在processDispatchResult方法中會對ModelAndView對象進行處理。而在processDispatchResult方法中包含一個render方法,其參數為ModelAndView對象以及request和response對象。在render方法中,通過resolveViewName會獲取到實際需要使用的視圖View對象,這個對象的具體類型是由XXX決定的。然后就會執行具體的View對象的render方法來完成數據的顯示過程。這里舉一個視圖類型的例子,他在render方法中具體執行了以下邏輯來綁定結果數據和視圖:
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域中獲取返回參數了。
