一、Spring MVC的初始化

Spring MVC的初始化主要有两部分组成:

1、初始化Spring IoC:通过配置ContextLoderListener完成。

在spring Web中,需要初始化IOC容器,用于存放我们注入的各种对象。当tomcat启动时首先会初始化一个web对应的IOC容器,用于初始化和注入各种我们在web运行过程中需要的对象。当tomcat启动的时候是如何初始化IOC容器的,我们先看一下在web.xml中经常看到的配置:

<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,当tomcat启动时会初始化一个Servlet容器,这样ContextLoaderListener会监听到Servlet的初始化,这样在Servlet初始化之后我们就可以在ContextLoaderListener中也进行一些初始化操作。ServletContextListener实现了ServletContextListener接口,所以会有两个方法contextInitialized和contextDestroyed。web容器初始化时会调用方法contextInitialized,web容器销毁时会调用方法contextDestroyed。

看看ContextLoaderListener的源码

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {  
  
    public ContextLoaderListener() {  
    }  
  
    public ContextLoaderListener(WebApplicationContext context) {  
        super(context);  
    }  
    /** 
     * Initialize the root web application context. 
     */  
    @Override  
    public void contextInitialized(ServletContextEvent event) {  
        //在父类ContextLoader中实现  
        initWebApplicationContext(event.getServletContext());  
    }  
    /** 
     * Close the root web application context. 
     */  
    @Override  
    public void contextDestroyed(ServletContextEvent event) {  
        //关闭Web IoC容器
        closeWebApplicationContext(event.getServletContext());  
        //消除相关参数
        ContextCleanupListener.cleanupAttributes(event.getServletContext());  
    }  
}

ContextLoaderListener的初始化Web容器方法contextInitialized()的默认实现是在他的父类ContextLoader的initWebApplicationContext方法中实现的。意思就是初始化web应用上下文。他的主要流程就是创建一个IOC容器,并将创建的IOC容器存到servletContext中,ContextLoader的核心实现如下:

//初始化WebApplicationContext,IOC容器  
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {  
        //判断web容器中是否已经有WebApplicationContext  
        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()) {  
            ("Root WebApplicationContext: initialization started");  
        }  
        long startTime = System.currentTimeMillis();  
  
        try {  
            // Store context in local instance variable, to guarantee that  
            // it is available on ServletContext shutdown.  
            //创建WebApplicationContext  
            if (this.context == null) {  
                //最终得到的是XmlWebApplicationContext  
                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);  
                    }  
                    //设置并刷新WebApplicationContext容器  
                    configureAndRefreshWebApplicationContext(cwac, 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;  
                ("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");  
            }  
            //初始化 WebApplicationContext完成并返回  
            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;  
        }  
    }

2、初始化映射请求上下文:通过配置DispatcherServlet完成。---加载springMVC的配置文件

             

springMvc添加启动类_ide

从上图可以看出,DispatcherServlet最终是继承由WEB容器提供的HttpServlet实现的。它里面有以下几个组件,这些组件就是Spring MVC的核心部分:

  • MultipartResolver:文件解析器,用于支持服务器的文件上传;
  • LocaleResolver:国际化解析器,用来提供国际化功能;
  • ThemeResolver:主题解析器,用来提供皮肤主题功能;
  • HandlerMapping:映射URI和处理器,使控制器得以运行;
  • HandlerAdapter:处理器适配器,为不同的处理器提供上下文运行环境;
  • HandlerExceptionResolver:处理器异常解析器,用来解析处理器产生的异常;
  • RequestToViewNameTranslator:视图逻辑名称转换器,根据逻辑视图的名称找到具体的视图。注意:当处理器没有返回逻辑视图名时,将请求的URL自动映射为逻辑视图名;
  • ViewResolver:视图解析器,当控制器返回后,通过试图解析器会把逻辑视图名进行,从而定位实际视图;

在WEB容器启动的时候,Spring MVC就会初始化上面的这些组件,所以我们不需要对他们进行配置就可以用了。下面从源码分析这个初始化过程。

3、HttpServletBean继承HttpServlet因此在Web容器启动时将调用它的init方法,该初始化方法的主要作用

  • 1)将Servlet初始化参数(init-param)设置到该组件上(如contextAttribute、contextClass、namespace、contextConfigLocation),通过BeanWrapper简化设值过程,方便后续使用;
  • 2)提供给子类初始化扩展点,initServletBean(),该方法由FrameworkServlet覆盖。
public abstract class HttpServletBean extends HttpServlet implements EnvironmentAware{
@Override
    public final void init() throws ServletException {
       //省略部分代码
       //1、如下代码的作用是将Servlet初始化参数设置到该组件上
//如contextAttribute、contextClass、namespace、contextConfigLocation;
       try {
           PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
           BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
           ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
           bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.environment));
           initBeanWrapper(bw);
           bw.setPropertyValues(pvs, true);
       }
       catch (BeansException ex) {
           //…………省略其他代码
       }
       //2、提供给子类初始化的扩展点,该方法由FrameworkServlet覆盖
       initServletBean();
       if (logger.isDebugEnabled()) {
           logger.debug("Servlet '" + getServletName() + "' configured successfully");
       }
    }
    //…………省略其他代码
}

在类HttpServletBean中看到initServletBean(),但是子类FrameworkServlet中覆盖了这个方法:

public abstract class FrameworkServlet extends HttpServletBean {
@Override
    protected final void initServletBean() throws ServletException {
        //省略部分代码
       try {
             //1、初始化Web上下文
           this.webApplicationContext = initWebApplicationContext();
             //2、提供给子类初始化的扩展点
           initFrameworkServlet();
       }
        //省略部分代码
    }
}
protected WebApplicationContext initWebApplicationContext() {
        //ROOT上下文(ContextLoaderListener加载的)
       WebApplicationContext rootContext =
              WebApplicationContextUtils.getWebApplicationContext(getServletContext());
       WebApplicationContext wac = null;
       if (this.webApplicationContext != null) {
           // 1、在创建该Servlet注入的上下文
           wac = this.webApplicationContext;
           if (wac instanceof ConfigurableWebApplicationContext) {
              ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
              if (!cwac.isActive()) {
                  if (cwac.getParent() == null) {
                      cwac.setParent(rootContext);
                  }
                  configureAndRefreshWebApplicationContext(cwac);
              }
           }
       }
       if (wac == null) {
             //2、查找已经绑定的上下文
           wac = findWebApplicationContext();
       }
       if (wac == null) {
            //3、如果没有找到相应的上下文,并指定父亲为ContextLoaderListener
           wac = createWebApplicationContext(rootContext);
       }
       if (!this.refreshEventReceived) {
             //4、刷新上下文(执行一些初始化)
           onRefresh(wac);
       }
       if (this.publishContext) {
           // Publish the context as a servlet context attribute.
           String attrName = getServletContextAttributeName();
           getServletContext().setAttribute(attrName, wac);
           //省略部分代码
       }
       return wac;
    }

      从initWebApplicationContext()方法可以看出,基本上如果ContextLoaderListener加载了上下文将作为根上下文(DispatcherServlet的父容器)。最后调用了onRefresh()方法执行容器的一些初始化,这个方法由子类实现,来进行扩展。

    DispatcherServlet继承FrameworkServlet,并实现了onRefresh()方法提供一些前端控制器相关的配置:

   

public class DispatcherServlet extends FrameworkServlet {
     //实现子类的onRefresh()方法,该方法委托为initStrategies()方法。
    @Override
    protected void onRefresh(ApplicationContext context) {
       initStrategies(context);
    }
 
    //初始化默认的Spring Web MVC框架使用的策略(如HandlerMapping)
    protected void initStrategies(ApplicationContext context) {
       initMultipartResolver(context);
       initLocaleResolver(context);
       initThemeResolver(context);
       initHandlerMappings(context);
       initHandlerAdapters(context);
       initHandlerExceptionResolvers(context);
       initRequestToViewNameTranslator(context);
       initViewResolvers(context);
       initFlashMapManager(context);
    }
}

最后调用了onRefresh()方法执行容器的一些初始化,这个方法由子类实现,来进行扩展。

  注意:如果没有配置Spring IOC的初始化类ContextLoderListener,那么DispatcherServlet在它初始化的时候会对Spring IoC进行初始化,一般来说最好在DispatcherServlet初始化之前就完成Spring IoC初始化。

二、使用注解方式配置初始化

         使用注解方式很简单,首先继承一个类:AbstractAnnotationConfigDIspatcherServletInitializer,然后实现它所定义的方法就可以了。

package spittr.config;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class SpittrWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer  {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[] {RootConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] {WebConfig.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] {"/"};
    }

}

 原理:

Servlet 容器会在 classpath 下搜索实现了 javax.servlet .ServletContainerInitializer 接口的任何类,找到之后用它来初始化 Servlet 容器。 Spring 实现了以上接口,实现类叫做 SpringServletContainerInitializer, 它会依次搜寻实现了 WebApplicationInitializer的任何类,并委派这个类实现配置。之后,Spring 3.2 开始引入一个简易的 WebApplicationInitializer 实现类,这就是AbstractAnnotationConfigDispatcherServletInitializer。

      所以 SpittrWebAppInitializer 继承 AbstractAnnotationConfigDispatcherServletInitializer之后,也就是间接实现了WebApplicationInitializer,在 Servlet 3.0 容器中,它会被自动搜索到,被用来配置 servlet 上下文。
 

我们需要重写上面的三个方法:

       第一个,getServletMappings(),为 DispatcherServlet 提供一个或更多的Servlet 映射;这里是被映射到 /,指示它为默认的 servlet,用来操作所有来到程序的 Request。

通过getServletConfigClasses() 方法,设置 DispatcherServlet 通过 WebConfig 配置类来完成 Spring 上下文和 bean 的加载。

通过调用 getRootConfigClasses()方法返回的类就是用来配置 ContextLoaderListener 产生的上下文。

     其中,DispatcherServlet 是用来加载涉及 web 功能的 beans,例如 controllers, view resolvers, 和 handler mappings;而 ContextLoaderListener 则是用来载入程序中其余的 beans,例如一些中间层和数据层组件,完成的是程序后端功能。