Spring源码分析3----Web环境中的SpringMVC(Web容器中的上下文的设计)

来源:互联网 发布:php url传递参数 编辑:程序博客网 时间:2024/04/30 02:37

一、Web环境中的Spring MVC

    用大体上看,在使用Spring MVC的时候,需要在web.xml中配置DispatcherServlet,这个DispatcherServlet可以看成是一个前端控制器的具体实现,还需要在Bean定义中配置Web请求和COntroller(控制器)的对应关系,以及各种视图的展现方式。
    Spring IoC是一个独立的模块,它并不是直接在Web容器中发挥作用的,如果要在Web环境中使用IoC容器,需要Spring为IoC设计一个启动过程,把IoC容器导入,并在Web容器中建立起来。具体来说,这个启动过程是和Web容器的启动过程集成在一起的。在这个过程中,一方面处理Web容器的启动,另一方面通过设计特定的Web容器拦截器,将IoC容器载入到Web环境中来,并将其初始化。在这个过程建立完成以后,IoC容器才能正常工作,而Spring MVC是建立是IoC容器的基础上的,这样才能建立起MVC框架的运行机制,从而响应从Web容器传递的HTTP请求。
    下面以Tomcat作为Web容器的例子进行分析。

……  <servlet>    <servlet-name>springmvc</servlet-name>    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>    <init-param>      <param-name>contextConfigLocation</param-name>      <param-value>classpath:/applicationContext.xml</param-value>    </init-param>    <load-on-startup>1</load-on-startup>    <async-supported>true</async-supported>  </servlet>  <servlet-mapping>    <servlet-name>springmvc</servlet-name>    <url-pattern>/</url-pattern>  </servlet-mapping><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class><listener>……
    作为Spring MVC的启动类,ContextLoaderListener被定义为一个监听器,这个监听器是与Web服务器的生命周期相关联的,由ContextLoaderListener监听器负责完成IoC容器在Web环境中的启动工作。

二、上下文在Web容器中的启动

2.1.IoC容器启动的基本过程

    IoC容器的启动过程就是建立上下文的过程,该上下文是与ServletContext相伴而生的,同时也是IoC容器在Web应用环境中的具体表现之一。由ContextLoaderListener启动的上下文为根上下文。在根上下文的基础上,还有一个与Web MVC相碰的上下文 用来保持控制器(DispatchServlet)需要的MVC对象,作为根上下文的子上下文,构成一个层次化的上下文体系。在Web容器中启动Spring应用程序时,首先建立根上下文,然后建立这个上下文体系的,这个上下文体系的建立是由COntextLoader来完成的,具体过程由下:

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {public ContextLoaderListener() {}public ContextLoaderListener(WebApplicationContext context) {super(context);}@Overridepublic void contextInitialized(ServletContextEvent event) {initWebApplicationContext(event.getServletContext());  // ------->}@Overridepublic void contextDestroyed(ServletContextEvent event) {closeWebApplicationContext(event.getServletContext());ContextCleanupListener.cleanupAttributes(event.getServletContext());}}

//-------------------------------// public class ContextLoader public WebApplicationContext initWebApplicationContext(ServletContext 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) {// 1----------------------->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, etcif (cwac.getParent() == null) {// 这里载入根上下文的双亲上下文 --------------------ApplicationContext parent = loadParentContext(servletContext);cwac.setParent(parent);}// IoC的refresh在这里面 2------------------->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;}}/* 1创建根上下文*/protected WebApplicationContext createWebApplicationContext(ServletContext sc) {// 判断使用什么样的类在Web容器作为IoC容器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);}/* 确认使用的IoC容器 应用可以在部署描述中指定使用什么样的IoC容器,是能通过CONTEXT_CLASS_PARAM参数的设置来完成的, 如果没有,则使用默认的IoC容器*/protected Class<?> determineContextClass(ServletContext servletContext) {// 设置指定的IoC容器String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);// 如果要ServletContext中配置了需要使用的CONTEXT_CLASS_PARAM,那就使用这个class,// 当然前提是这个class是可用的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 { // 如果没有额外的配置,那么使用默认的contextClasscontextClassName = 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);}}}// 2protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {if (ObjectUtils.identityToString(wac).equals(wac.getId())) {String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);if (idParam != null) {wac.setId(idParam);}else {wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +ObjectUtils.getDisplayString(sc.getContextPath()));}}wac.setServletContext(sc);String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);if (configLocationParam != null) {wac.setConfigLocation(configLocationParam);}ConfigurableEnvironment env = wac.getEnvironment();if (env instanceof ConfigurableWebEnvironment) {((ConfigurableWebEnvironment) env).initPropertySources(sc, null);}customizeContext(sc, wac);wac.refresh();   // ------------------>}

这就是IoC容器在Web容器中的启动过程,与在应用中启动IoC容器的方式相类似,所不同的是这里需要考虑Web容器的环境特点,比如各种参数的设置、IoC容器与Web容器ServletContext的结合等。在初始化这个上下文以后,该上下文会被存储到ServletContext中,这样就建立了一个全局的关于整个应用的上下文。同时在启动Spring MVC时,我们还会看到这个上下文被以后的DispatcherServlet在进行自己持有的上下文的初始化时,设置为DispatcherServlet自带的上下文的双亲上下文。这个过程在下面分析Spring MVC实现的地文清楚地看到

public interface ConfigurableApplicationContext extends ApplicationContext, Lifecycle, Closeable {String CONFIG_LOCATION_DELIMITERS = ",; \t\n";String CONVERSION_SERVICE_BEAN_NAME = "conversionService";String LOAD_TIME_WEAVER_BEAN_NAME = "loadTimeWeaver";String ENVIRONMENT_BEAN_NAME = "environment";String SYSTEM_PROPERTIES_BEAN_NAME = "systemProperties";String SYSTEM_ENVIRONMENT_BEAN_NAME = "systemEnvironment";void setId(String id);void setParent(ApplicationContext parent);@OverrideConfigurableEnvironment getEnvironment();void setEnvironment(ConfigurableEnvironment environment);void addBeanFactoryPostProcessor(BeanFactoryPostProcessor postProcessor);void addApplicationListener(ApplicationListener<?> listener);void addProtocolResolver(ProtocolResolver resolver);// --------------------->void refresh() throws BeansException, IllegalStateException;void registerShutdownHook();@Overridevoid close();boolean isActive();ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;}
//------------------------------------------------------------// public abstract class AbstractApplicationContext extends DefaultResourceLoader//implements ConfigurableApplicationContext, DisposableBean@Overridepublic void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {prepareRefresh();ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();prepareBeanFactory(beanFactory);try {postProcessBeanFactory(beanFactory);invokeBeanFactoryPostProcessors(beanFactory);registerBeanPostProcessors(beanFactory);initMessageSource();initApplicationEventMulticaster();onRefresh();registerListeners();finishBeanFactoryInitialization(beanFactory);finishRefresh();}catch (BeansException ex) {if (logger.isWarnEnabled()) {logger.warn("Exception encountered during context initialization - " +"cancelling refresh attempt: " + ex);}destroyBeans();cancelRefresh(ex);throw ex;}finally {resetCommonCaches();}}}
    在Web.xml,已经配置了ContextLoaderListener,这个ContextLoaderListener是Spring提供的类,是为在Web容器中建立IoC容器服务的,它实现了ServletContextListener接口,这个接口提供了与Servlet生命周期的回调。而在Web容器中,建立WebApplicationContext的过程,是在contextInitialized的接口实现中完成的。具体的载入IoC容器的过程是由ContextLoaderListener交由ContextLoaderg来完成的,而ContextLoader本身就是ContextLoaderListener的基类。
    在ContextLoader中,完成了两个IoC容器建立的基本过程,一个是在Web容器中建立起双亲IoC容器,另一个是生成相应的WebApplicationContext并将其初始化

2.2.web容器中的上下文设计

先从Web容器的上下文入手,看看Web环境中的上下文设置有哪些特别之处,然后再到ContextLoaderListener中去了解整个容器启动的过程。
public interface WebApplicationContext extends ApplicationContext {// 这里定义的常量用于在ServletContext中存取根上下文String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";String SCOPE_REQUEST = "request";String SCOPE_SESSION = "session";String SCOPE_GLOBAL_SESSION = "globalSession";String SCOPE_APPLICATION = "application";String SERVLET_CONTEXT_BEAN_NAME = "servletContext";String CONTEXT_PARAMETERS_BEAN_NAME = "contextParameters";String CONTEXT_ATTRIBUTES_BEAN_NAME = "contextAttributes";// 对WebApplicationContext来说,需要得到Web容器的ServletContext,// 通过这个方法可以取得Web容器的ServletContextServletContext getServletContext();}
    在启动过程中,Spring会使用一个默认的WebApplicationContext实现作为IoC容器。这个默认使用的IoC容器就是XmlWebApplicationContext,
public class XmlWebApplicationContext extends AbstractRefreshableWebApplicationContext {/** 这里是设置默认BeanDefinition的地方,在/WEB-INF/applicationContext.xml文件中,  如果不特别指定其他文件,IoC容器会从这里读取BeanDefinition来初始化IoC容器 */public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";/** 默认的配置文件位置在/WEB-INF/目录下 */public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/";/** 默认的配置文件后缀名.xml文件 */public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml";// 像前面对IoC容器的分析一样,这个加载过程在容器refresh()时启动@Overrideprotected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {// 对于XmlWebApplicationCOntext,当然是使用XmlBeanDefinitionReader来对BeanDefinition信息进行解析XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);// Configure the bean definition reader with this context's// resource loading environment.beanDefinitionReader.setEnvironment(getEnvironment());beanDefinitionReader.setResourceLoader(this);beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));// Allow a subclass to provide custom initialization of the reader,// then proceed with actually loading the bean definitions.initBeanDefinitionReader(beanDefinitionReader);loadBeanDefinitions(beanDefinitionReader);}protected void initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader) {}/* 如果有多个BeanDefinition的文件定义,需要逐个载入,都是通过reader来完成的,  这个初始化过程是由refreshBeanFactory方法来完成的,这里只负责载入BeanDefinition*/protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {String[] configLocations = getConfigLocations();if (configLocations != null) {for (String configLocation : configLocations) {reader.loadBeanDefinitions(configLocation);}}}/* 这里取得Resource位置的地方,使用了设定的默认配置位置,  默认的配置是/WEB_INF/applicationContext.xml */@Overrideprotected String[] getDefaultConfigLocations() {if (getNamespace() != null) {return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};}else {return new String[] {DEFAULT_CONFIG_LOCATION};}}}
    从代码中可以看到,在XmlWebApplication中,基本的上下文功能都已经通过类的继承获得,这里需要处理的是,如何获取Bean定义信息,在这里,就转化为如何在Web容器环境如这里指定的/WEB_INF/applicationContext.xml中获得Bean定义信息。在获得Bean定义信息之后,后面的过程基本上就和前面分析的XmlFileSystemBeanFactory一样,是通过XmlBeanDefinitionReader来载入Bean定义信息的,最终完成整个上下文的初始化过程.

3.ContextLoader的设计与实现

    对于Spring承载的Web应用而言,可以指定在Web应用程序启动时载入IoC容器(或者称为WebApplicationContext).这个功能是由ContextLoaderListener这样的类来完成的,这旨在Web容器中配置的监听器。这个ContextLoaderListener通过使用ContextLoader来完成实际的WebApplicationContext, 也就是IoC容器的初始化工作。这个ContextLoader就像Spring应用程序在Web容器中的启动器。这个启动过程是在Web容器中发生的,所有需要根据Web容器部署的要求来定义ContextLoader,相关的配置在概述中已经看到了,这里就不重复了。
    为了了解IoC容器在Web容器中的启动原理,这里对启动器ContextLoaderListener的实现进行分析。这个监听器是启动根IoC容器并把它载入到Web容器的主要功模块,也就是整个Spring Web应用加载IoC的第一个地方。从加载过程可以看到,首先从Servlet事件中得到ServletContext,然后可以读取配置在web.xml中的各个相关属性值,接着ContextLoader会实例化WebApplicationContext,并完成其载入和初始化过程。这个被初始化的第一个上下文作为根上下文而存在,这个根上下文载入后,被绑定到Web应用程序的ServletContext上。任何需要访问根上下文的应用程序代码都可以从WebApplicationContextUtils类的静态方法中得到,具体取得根上下文的方法如下:
        WebApplicationContext getWebApplicationContext(ServletContext sc);
    下面分析具体的根上下文的载入过程。在ContextLoaderListener中,实现的是ServletContextListener接口,由于这个接口是ServletContext的监听者,如果ServletContext发生变化,会触发相应的事件,而监听器一直对这些事件进行监听,如果接收到了监听的事件,就会做出预先设计好的响应动作。下面看看服务器启动时ContextLoaderListener的调用完成了什么,如下代码。在这个初始化回调中,创建了ContextLoader,同时会利用创建出来的ContextLoader来完成IoC容器的初始化

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {public ContextLoaderListener() {}public ContextLoaderListener(WebApplicationContext context) {super(context);}@Overridepublic void contextInitialized(ServletContextEvent event) {initWebApplicationContext(event.getServletContext()); // ---->}@Overridepublic void contextDestroyed(ServletContextEvent event) {closeWebApplicationContext(event.getServletContext());ContextCleanupListener.cleanupAttributes(event.getServletContext());}}
    下面的分析在上面的“IoC在web容器中的启动"已经分析了,这里就不分析了












0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 把货退了卖家不退款怎么办? 退款了又收到货怎么办 退货忘了填单号怎么办 手机换号了淘宝怎么办 换了手机支付宝怎么办 手机丢了微信登不上去了怎么办 前面手机丢了微信登不上去怎么办 淘宝密码忘了怎么办呢 融e借逾期一天怎么办 拼多多处罚下架怎么办 永久无法解绑支付宝怎么办 淘宝下单购买人数太多怎么办 新浪微博被拉黑暂时无法评论怎么办 闲鱼交易成功后卖家反悔怎么办 闲鱼买家不申请介入怎么办 支付宝安装不上怎么办 无线摄像机离wifi太远怎么办 安卓系统死机了怎么办 安卓手机开不了机怎么办 手机关机键坏了怎么办 华为手机接听电话声音小怎么办 小米6x游戏闪退怎么办 安卓8.0不兼容怎么办 安卓8.0应用闪退怎么办 安卓8.0不兼容的怎么办 游戏全屏只有一个分辨率选项怎么办 安卓6.0吃运行内存怎么办 小米手机王者荣耀录像不支持怎么办 win764位系统不兼容怎么办 安卓版本太高不兼容怎么办 安卓3.2.0不兼容怎么办 手机卡和手机不兼容怎么办 vivo手机下载吃鸡不兼容怎么办 vivox3t不兼容支付宝怎么办 移动sim卡坏了怎么办 cpu和主板不兼容怎么办 主板与cpu不兼容怎么办 cpu跟主板不兼容怎么办 软件与电脑不兼容怎么办 win8系统无限重启怎么办 安装微信旧版本登录提示升级怎么办