当前位置: 移动技术网 > IT编程>开发语言>Java > Spring MVC启动流程分析

Spring MVC启动流程分析

2020年03月19日  | 移动技术网IT编程  | 我要评论

微博美胸大赛,为爱战斗完整版,李丽凤

本文是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)

<!doctype web-app public
 "-//sun microsystems, inc.//dtd web application 2.3//en"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >


<!--一个web应用对应一个servletcontext实例,这个实例是应用部署启动后,servlet容器为应用创建的。
    servletcontext实例包含了所有servlet共享的资源信息。通过提供一组方法给servlet使用,用来
    和servlet容器通讯,比如获取文件的mime类型、分发请求、记录日志等。
    http://www.cnblogs.com/nantang/p/5919323.html -->
<web-app>
    <display-name>archetype created web application</display-name>

    <context-param>
        <param-name>contextconfiglocation</param-name>
        <param-value>classpath:beans.spring.xml</param-value>
    </context-param>
    <context-param>
        <param-name>webapprootkey</param-name>
        <param-value>project.root.path</param-value>
    </context-param>

    <!-- 将项目的绝对路径放到系统变量中 -->
    <listener>
        <listener-class>org.springframework.web.util.webapprootlistener</listener-class>
    </listener>
    <!--初始化spring ioc容器,并将其放到servletcontext的属性当中-->
    <listener>
        <listener-class>org.springframework.web.context.contextloaderlistener</listener-class>
    </listener> 
     
    <!-- 配置springmvc-->
    <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:springmvc.spring.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <!-- 这边不建议写成/* -->
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

tomcat启动的时候会依次加载web.xml中配置的listener、filter和servlet。所以根据上面的配置,会首先加载contextloaderlistener,这个类继承了contextloader,用来初始化spring根上下文,并将其放入servletcontext中。下面就以这个为入口分析下代码。

tomcat容器首先会调用调用contextloadlistener的contextinitialized()方法,这个方法又调用了父类contextloader的initwebapplicationcontext()方法。下面是这个方法的源代码。

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的<context-param>中配置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的继承结构。

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方法。

//httpservletbean的init是入口方法
@override
public 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()方法

//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()方法的具体代码

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()方法,这个方法初始化了很多策略:

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的各个功能组件。比如异常处理器、视图处理器、请求映射处理等。

博客参考

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网