我举得这篇文章解决了个人不少疑惑,理清了我之前不太清楚的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域中获取返回参数了。