查看: 342|回复: 0

Spring MVC启动流程分析

[复制链接]
发表于 2020-3-21 02:54:49 | 显示全部楼层 |阅读模式
本文是Spring MVC系列博客的第一篇,后续会汇总成贴子。
Spring MVC是Spring系列框架中使用频率最高的部分。不管是Spring Boot照旧传统的Spring项目,只要是Web项目都会使用到Spring MVC部分。因此程序员一定要纯熟掌握MVC部分。本篇博客就简要分析下Spring MVC的启动流程,帮助我们更好的理解这个框架。
为什么要写这篇博客

Spring的MVC框架已经出来很久了,网上介绍这部分的博客有很多很多,而且很多肯定比我自己写的好,那我还为什么要写这篇博客呢。一方面我以为博客是对自己学习过程的一个记载,另一方面写博客的过程能加深自己对相关技术的理解,也方便以后自己回首总结。
Spring MVC简介

什么是Spring MVC

要回答这个问题,我们先要说说MVC。MVC是一种设计模式,这种设计模式建议将一个请求由M(Module)、V(View)、C(controller)三个部分进行处理。请求先颠末controller,controller调用其他服务层得到Module,最后将Module数据渲染成试图(View)返回客户端。Spring MVC是Spring生态圈的一个组件,一个服从MVC设计模式的WEB MVC框架。这个框架可以和Spring无缝整合,上手简单,易于扩展。
办理什么问题

通常我们将一个J2EE项目项目分为WEB层、业务逻辑层和DAO层。Spring MVC办理的是WEB层的编码问题。Spring MVC作为一个框架,抽象了很多通用代码,简化了WEB层的编码,而且支持多种模板技术。我们不需要像以前那样:每个controller都对应编写一个Servlet,请求JSP页面返回给前台。
优缺点

用的比力多的MVC框架有Struts2和Spring MVC。两者之间的对比

  • 最大的一个区别就是Struts2完全脱离了Servlet容器,而SpringMVC是基于Servlet容器的;
  • Spring MVC的核心控制器是Servlet,而Struts2是Filter;
  • Spring MVC默认每个Controller是单列,而Struts2每次请求都会初始化一个Action;
  • Spring MVC设置较简单,而Struts2的设置更多照旧基于XML的设置。
总的来说,Spring MVC比力简单,学习本钱低,和Spring能无缝集成。在企业中也得到越来越多的应用。以是个人比力建议在项目中使用Spring MVC。
启动流程分析

PS:本文的分析照旧基于传统的Tomcat项目分析,因为这个是根本。如今非常流行的Spring Boot项目中的启动流程后续也会写文章分析。其实原理差不多...
要分析Spring MVC的启动过程,要从它的启动设置说起。一样平常会在Tomcat的 Web.xml中设置了一个ContextLoaderListener和一个DispatcherServlet。其实ContextLoaderListener是可以不配,如许的话Spring会将所有的bean放入DispatcherServlet初始化的上下文容器中管理。这边我们就拿常规的设置方式说明Spring MVC的启动过程。(PS:Spring Boot启动过程已经不使用Web.xml)
  1.     Archetype Created Web Application            contextConfigLocation        classpath:beans.spring.xml                webAppRootKey        project.root.path                    org.springframework.web.util.WebAppRootListener                    org.springframework.web.context.ContextLoaderListener                          springmvc        org.springframework.web.servlet.DispatcherServlet                    contextConfigLocation            classpath:springmvc.spring.xml                1                springmvc                /   
复制代码
Tomcat启动的时候会依次加载web.xml中设置的Listener、Filter和Servlet。以是根据上面的设置,会首先加载ContextLoaderListener,这个类继承了ContextLoader,用来初始化Spring根上下文,并将其放入ServletContext中。下面就以这个为入口分析下代码。
Tomcat容器首先会调用调用ContextLoadListener的contextInitialized()方法,这个方法又调用了父类ContextLoader的initWebApplicationContext()方法。下面是这个方法的源代码
  1. public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {//如果ServletContext中已经存在Spring容器则报错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,默认创建的是XmlWebApplicationContext            //如果想要自界说实现类,可以在web.xml的中设置contextClass这个参数            //此时的Context还没进行设置,相当于只是个"空壳"            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);                }                //读取Spring的设置文件,初始化父上下文情况                configureAndRefreshWebApplicationContext(cwac, servletContext);            }        }        //将根上下文存入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;    }}
复制代码
至此,Spring的父(根)上下文已经初始化完毕,而且已经存在ServletContext中。
下面开始分析子上下文的初始化过程。这个过程通过Spring MVC的核心Servlet完成,以是我们也有必要讲下Servlet的生命周期。请求过来,判定Servlet有没创建,没有实例化并调用init方法,后面再调用service方法。我们在设置DispatcherServlet的时候,将其设置为启动时创建实例,以是Tomcat在启动的时候就会创建Spring的子上下文。
下面是DispatcherServlet的继承结构。
  1. GenericServlet (javax.servlet)    HttpServlet (javax.servlet.http)        HttpServletBean (org.springframework.web.servlet)            FrameworkServlet (org.springframework.web.servlet)                DispatcherServlet (org.springframework.web.servlet)
复制代码
DispatcherServlet继承了FrameworkServlet,FrameworkServlet又继承了HttpServletBean,HttpServletBean又继承HttpServlet而且重写了init方法,以是创建子上下文时的入口就在这个init方法。
  1. //HttpServletBean的init是入口方法@Overridepublic final void init() throws ServletException {    if (logger.isDebugEnabled()) {        logger.debug("Initializing servlet '" + getServletName() + "'");    }    // Set bean properties from init parameters.    try {        //读取Servlet设置的init-param,创建DispatcherServlet实例        PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);        BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);        ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());        bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));        initBeanWrapper(bw);        bw.setPropertyValues(pvs, true);    }    catch (BeansException ex) {        if (logger.isErrorEnabled()) {            logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);        }        throw ex;    }    // HttpServletBean的这个方法中没有做任何事故,子类FrameWorkServlet这个类的initServletBean()方法重写了    // 这个类,以是后续工作会在这个方法中执行。    initServletBean();    if (logger.isDebugEnabled()) {        logger.debug("Servlet '" + getServletName() + "' configured successfully");    }}
复制代码
下面是FrameWorkServlet这个类的initServletBean()方法
  1. //FrameWorkServlet.initServletBean()protected final void initServletBean() throws ServletException {    getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");    if (this.logger.isInfoEnabled()) {        this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");    }    long startTime = System.currentTimeMillis();    try {        //这里是重点,初始化子Spring上下文        this.webApplicationContext = initWebApplicationContext();        initFrameworkServlet();    }    catch (ServletException ex) {        this.logger.error("Context initialization failed", ex);        throw ex;    }    catch (RuntimeException ex) {        this.logger.error("Context initialization failed", ex);        throw ex;    }    if (this.logger.isInfoEnabled()) {        long elapsedTime = System.currentTimeMillis() - startTime;        this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +                elapsedTime + " ms");    }}
复制代码
下面是initWebApplicationContext()方法的详细代码
  1. 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        // 这边是重点,创建Spring子上下文,并将设置其父类上下文        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) {        // 将Spring子上下文存入ServletContext        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;}
复制代码
最后看下DispatcherServlet中的onRefresh()方法,这个方法初始化了很多策略:
  1. protected void initStrategies(ApplicationContext context) {    initMultipartResolver(context);    initLocaleResolver(context);    initThemeResolver(context);    initHandlerMappings(context);    initHandlerAdapters(context);    initHandlerExceptionResolvers(context);    initRequestToViewNameTranslator(context);    initViewResolvers(context);    initFlashMapManager(context);}
复制代码
到此为止,SpringMVC的启动过程结束了。这边做下SpringMVC初始化总结(不是很详细)

  • HttpServletBean的主要做一些初始化工作,将我们在web.xml中设置的参数书设置到Servlet中;
  • FrameworkServlet主要作用是初始化Spring子上下文,设置其父上下文,并将其和ServletContext关联;
  • DispatcherServlet:初始化各个功能的实现类。比如异常处理、视图处理、请求映射处理等。
简单总结

传统的Spring MVC项目启动流程如下:

  • 如果在web.xml中设置了org.springframework.web.context.ContextLoaderListener,那么Tomcat在启动的时候会先加载父容器,并将其放到ServletContext中;
  • 然后会加载DispatcherServlet(这块流程建议从init方法一步步往下看,流程照旧很清楚的),因为DispatcherServlet实质是一个Servlet,以是会先执行它的init方法。这个init方法在HttpServletBean这个类中实现,其主要工作是做一些初始化工作,将我们在web.xml中设置的参数书设置到Servlet中,然后再触发FrameworkServlet的initServletBean()方法;
  • FrameworkServlet主要作用是初始化Spring子上下文,设置其父上下文,并将其放入ServletContext中;
  • FrameworkServlet在调用initServletBean()的过程中同时会触发DispatcherServlet的onRefresh()方法,这个方法会初始化Spring MVC的各个功能组件。比如异常处理器、视图处理器、请求映射处理等。(注意这个onRefresh方法,这个方法做了很多事故,建议过细看下)
博客参考


相关技术服务需求,请联系管理员和客服QQ:2753533861或QQ:619920289
您需要登录后才可以回帖 登录 | 用户注册

本版积分规则

帖子推荐:
客服咨询

QQ:2753533861

服务时间 9:00-22:00

快速回复 返回顶部 返回列表