SpringMVC的学习(二)--SpringMVC的初始化和源码分析

一、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>  

      ContextLoaderListener是一个监听器,其实现了ServletContextListener接口,其用来监听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()) {  
            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.  
            //创建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;  
                logger.info("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的配置文件

             

从上图可以看出,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 3.0 环境下,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。

       DispatcherServlet 开始启动时,会产生一个 Spring 应用程序上下文,把它和配置文件中声明的 bean 或者类一起加载进来。通过getServletConfigClasses() 方法,设置 DispatcherServlet 通过 WebConfig 配置类来完成 Spring 上下文和 bean 的加载。

      但是在 Spring web 程序中,往往还有另外一个应用程序上下文,它是由 ContextLoaderListener 产生的。通过调用 getRootConfigClasses()方法返回的类就是用来配置 ContextLoaderListener 产生的上下文。

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

借鉴博客:https://blog.csdn.net/classicer/article/details/50753019https://jinnianshilongnian.iteye.com/blog/1602617https://www.cnblogs.com/damens/p/6460744.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值