SpringMVC加载webapplicationcontext代码分析

Spring MVC属于SpringFrameWork的后续产品,已经融合在Spring Web Flow里面。Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块。使用 Spring 可插入的 MVC 架构,可以选择是使用内置的 Spring Web 框架还可以是 Struts 这样的 Web 框架。
springmvc 的信息配置在web.xml中,与struts2不同的是struts2 的入口是一个filter,而springmvc的入口是一个servlet。先看下web容器是怎么加载web.xml的

1、启动一个WEB项目的时候,WEB容器会去读取它的配置文件web.xml,读取listener和context-param两个结点。
2、紧急着,容创建一个ServletContext(servlet上下文),这个web项目的所有部分都将共享这个上下文。
3、容器将context-param转换为键值对,并交给servletContext。
4、容器创建listener中的类实例,创建监听器。
web.xml 的加载顺序是:ServletContext -> context-param -> listener -> filter -> servlet ,而同个类型之间的实际程序调用的时候的顺序是根据对应的 mapping 的顺序进行调用的。

一个springMVC项目的web.xml简单配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
  <!--统一字符编码处理乱码问题 -->
  <filter>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
      <param-name>forceEncoding</param-name>
      <param-value>true</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>


  <!-- 在容器启动时加载spring配置文件 -->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationcontext.xml</param-value>
  </context-param>
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>


  <servlet>
    <servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath*:springmvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
    <url-pattern>*.do</url-pattern>
  </servlet-mapping>
  <!-- 设置session过期时间--》
  <session-config>
    <session-timeout>15</session-timeout>
  </session-config>
</web-app>
容器创建
ContextLoaderListener基于web上下文级别的监听器在web容器加载的时候就创建了applicationContext对象,并且将spring配置文件中的bean加载在ioc容器当中
DispatcherServlet是一个请求分发控制器,和其他servlet一样拦截来自url-pattern中定义的请求。其中load-on-startup 标签的含义是指在web容器启动时就制定了servlet被加载的顺序,他的值必须是一个整数,当是一个大于等于0的整数的时候,容器在配置的时候就加载并且初始化这个servler,数值越小,优先被加载。当是一个负整数,或者没有制定的时候,在该servlet被调用的时候才加载
在web.xml中我们可以看到contextLoaderListener和DispatcherServlet都会去加载spring的xml文件,值得说明的是这两种方式加载的spring生成的application是两个独立的application 具体看这里

个人建议基于mvc的pring配置相关由DispatcherServlet加载,而其他的javaBean有contextLoaderListener加载

一:contextLoaderListener加载spring的配置文件applicationContext.xml
contextLoaderListener 继承了contextLoader并且实现了ServletContextListener
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
private ContextLoader contextLoader;
}

其中有两个主要的方法 在项目启动时会执行contextInitialized()方法,该方法主要是用来创建application对象。在容器关闭时会调用contextDestroyed()方法,该方法会执行applicationContext的清理操作

    public void contextInitialized(ServletContextEvent event) {
       /*   @Deprecated
    public ContextLoader getContextLoader() {
        return this.contextLoader;
    }*/
        this.contextLoader = createContextLoader();
        if (this.contextLoader == null) {
            this.contextLoader = this;
        }
              //初始化context的webApplicationContext对象      this.contextLoader.initWebApplicationContext(event.getServletContext());
    }



    public void contextDestroyed(ServletContextEvent event) {
        if (this.contextLoader != null) {
            this.contextLoader.closeWebApplicationContext(event.getServletContext());
        }
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }

在ContextLoaderListen中的contextInitialized()方法中会调用initWebApplicationContext(event.getServletContext());

    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) {
            //创建webapplicationcontext容器对象
                this.context = createWebApplicationContext(servletContext);
            }
            if (this.context instanceof ConfigurableWebApplicationContext) {
 //加载spring配置文件并且创建相关bean对象到容器中去
                configureAndRefreshWebApplicationContext((ConfigurableWebApplicationContext)this.context, servletContext);
            }
//将WebApplicationContext放到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;
        }
    }


protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
                     ..
                     ..
                     // Determine parent for root web application context, if any.
        ApplicationContext parent = loadParentContext(sc);

        wac.setParent(parent);
        wac.setServletContext(sc);
        //CONFIG_LOCATION_PARAM就是在web.xml中配置的context-param中的contextConfigLocation
        String initParameter = sc.getInitParameter(CONFIG_LOCATION_PARAM);
        if (initParameter != null) {
           //加载spring的配置文件applicationContext.xml
            wac.setConfigLocation(initParameter);
        }
        customizeContext(sc, wac);
        //执行所有Java对象的创建
        wac.refresh();


}
//创建ConfigurableWebApplicationContext 对象的方法
    protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
        //读取上下文对象
        Class<?> contextClass = determineContextClass(sc);
                        .
                        .
         //创建Configurable的上下文对象               
        ConfigurableWebApplicationContext wac =
                (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
        return wac;
    }
简单描述下上面的调用关系

1:web容器启动时,调用ContextLoaderLinster的contextInitialized方法初始化WebApplicationContext容器
2:contextInitialized()调用ContextLoaderLinster的父类ContextLoader的initWebApplicationContxt(ServletContext servletContext)方法,该方法主要做了3件事:

  • createWebApplicationContext(servletContext);创建webApplicationContext对象
  • configureAndRefreshWebApplicationContext((ConfigurableWebApplicationContext)this.context, servletContext);从servletContext中获取spring的配置文件路径加载配置文件,并且refresh(),创建里面的Bean实例
  • 将webapplicationcontext对象放到servletContext中去 servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);//常量
    public static final String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + “.ROOT”;
    至此创建出了一个ioc容器并且将spring配置的bean加载了到了其中
    我们可以这样获取这个IOC容器
WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
二:DispatcherServlet
DispatcherServlet是前端控制器设计模式的实现,提供spring web mvc 集中访问点,并且负责职责的分派,与spring ioc容器可以达到无缝集成,可以继承spring的所有优势

我们在web.xml中设置了DispatcherServlet 启动顺序load-on-startup
则DispatcherServlet会随着项目一起启动,而servlet初始化话是调用他的init方法
先看一下servlet的继承关系
这里写图片描述
我们可以在他的父类HttpServletBean中找到init()方法

public final void init() throws ServletException {
                  .
                  .
        initServletBean();
                  .
                  .
    }

initServletBean() 在HttpservletBean的子类FrameworkServlet中得到的实现

try {
            this.webApplicationContext = initWebApplicationContext();
            initFrameworkServlet();
        }



protected WebApplicationContext initWebApplicationContext() {
        WebApplicationContext rootContext =
                WebApplicationContextUtils.getWebApplicationContext(getServletContext());
        WebApplicationContext wac = null;

        if (this.webApplicationContext != null) {
            // A context instance was injected at construction time -> use it
            wac = this.webApplicationContext;
            if (wac instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
                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 -> set
                        // the root application context (if any; may be null) as the parent
                        cwac.setParent(rootContext);
                    }
                    configureAndRefreshWebApplicationContext(cwac);
                }
            }
        }
        if (wac == null) {
            // No context instance was injected at construction time -> see if one
            // has been registered in the servlet context. If one exists, it is assumed
            // that the parent context (if any) has already been set and that the
            // user has performed any initialization such as setting the context id
            wac = findWebApplicationContext();
        }
        if (wac == null) {
            // No context instance is defined for this servlet -> create a local one
            wac = createWebApplicationContext(rootContext);
        }

        if (!this.refreshEventReceived) {
            // Either the context is not a ConfigurableApplicationContext with refresh
            // support or the context injected at construction time had already been
            // refreshed -> trigger initial onRefresh manually here.
            onRefresh(wac);
        }

        if (this.publishContext) {
            // Publish the context as a servlet context attribute.
            String attrName = getServletContextAttributeName();
            getServletContext().setAttribute(attrName, wac);
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
                        "' as ServletContext attribute with name [" + attrName + "]");
            }
        }

        return wac;
    }

大概流程

①HttpServletBean.init方法中执行initServletBean方法进行初始化操作,当然该方法在HttpServletBean是空方法,所以需要子类重写。
②FrameworkServlet.initServletBean子类不负众望,重写了initServletBean方法,该方法最核心的操作就是调用initWebApplicationContext()执行上下文Bean初始化。
③FrameworkServlet.initWebApplicationContext方法首先获取自己的双亲上下文(也就是ContextLoaderListener初始化成功的WebApplicationContext);然后创建或者获取当前Servelet的WebApplicationContext。
④无论是自己创建还是获取现有的WebApplicationContext,最终都会让Servelt级别的WebApplicationContext执行configureAndRefreshWebApplicationContext()方法进行上下文容器初始化。

通过以上几步即可创建一个完整的IOC容器,而完成容器创建之后,DispatcherServlet还做了一件事:初始化Servelt控制器必备对象,这个是在initWebApplicationContext()方法中通过调用onRefresh(wac)方法实现的。而onRefresh也被重写过,如果要了解怎么初始化Servlet控制器必备对象可以查看DispatcherServlet的onRefresh方法了解。

if (!this.refreshEventReceived) {
            // Either the context is not a ConfigurableApplicationContext with refresh
            // support or the context injected at construction time had already been
            // refreshed -> trigger initial onRefresh manually here.
            onRefresh(wac);
        }

    protected void onRefresh(ApplicationContext context) {
        initStrategies(context);
    }

  protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context);
        initLocaleResolver(context);
        initThemeResolver(context);
        //将uri和hanlder进行了映射
        initHandlerMappings(context);
        initHandlerAdapters(context);
        initHandlerExceptionResolvers(context);
        initRequestToViewNameTranslator(context);
        initViewResolvers(context);
        initFlashMapManager(context);
    }



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值