SpringMVC源码分析总结

该文章基于《Spring源码深度解析》撰写,感谢郝佳老师的奉献
SpringMVC是基于Servlet功能实现的,通过带有Servlet接口的DispatcherServlet来封装核心功能,控制器则由实现了Controller接口的类,SpringMVC解决的痛点有三个:
(1)将Web页面的请求传给服务器
(2)根据不同的请求处理不同的逻辑页面
(3)返回处理结果数据并跳转页面
Spring+SpringMVC开发的时候配置文件一般有
(1)web.xml(必须)
必须的原因在于:配置contexLoadListener配置DispatcherServlet拦截请求
(2)applicationContext.xml(非必需,可以用来配置InternalResourceViewResolver来给返回的页面加上前缀进行试图查找)
(3)model(pojo,非必需)
(4)controller(继承AbstractController,用于处理Web请求)
(5)视图(可以由jsp或其他试图框架完成)
(6)Spring-servlet.xml(非必需,一般用于模块化开发)
现在先从最开始的web.xml文件进行分析,我们首先配置了contextConfigLocation如下所示:

<listener>
    <listener-class>org.Springframework.web.context.ContextLoaderListener</listener-class>
</listener>

这个类的继承结构如下所示:
ContextLoaderLisener结构
上图中的ServletContextListener该类位于servlet-api.jar包中(再tomcat的自带lib中可以找到)
ContextLoaderListener实现了ServletContextListener接口,该接口保证了在能够为客户端提供服务之前向ServletContext中添加任何对象。每个Web应用都有一个ServletContext与之相关联,启动时被创建,关闭时被销毁,并且ServletContext在全局范围内有效。ServletContextListener的核心逻辑就是初始化WebApplicationContext实例并放入ServletContext中。
我们之所以使用了ContextLoaderListener是为了避免下面这种硬编码配置:

ApplicationContext ac = new ClassPathXmlApplecation("applicationContext.xml")

再来说说ContextLoaderListener的实际步骤是:
(1)webApplication存在性验证(配置中只能有一个ServletContextListener接口,验证的方法就是检查该属性是否存在)
(2)创建webApplication实例
(3)将属性注入(例如contextConfigLocation)webApplication
(4)将实例记录到servletContext中
(5)映射当前的类加载器与创建的实例到全局变量currentContextPerThread中
讲完了ContextLoaderListener再来讲讲web.xml中另一个重要配置DispatcherServlet,对于DispatcherServlet的配置如下所示:

<servlet>
    <servlet-name>SpringMVC(可自定义)</servlet-name>
    <servlet-class>org.SpringMVC.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>SpringMVC(可自定义)<servlet-name>
    <url-pattern>自定义匹配规则(比如*.htm,那么将会匹配以htm为结尾的请求</url-pattern>
</servlet-mapping>

接下来我们先给出DispatcherServlet的类层次结构,如下图所示:
DispatcherServlet
可以看到,DispatcherServlet实际上是实现了Servlet接口的类,下面来讲讲Servlet接口:
Servlet接口是基于HTTP协议的,servlet框架主要由两个java包组成javax.servlet(提供所有必须实现或扩展的通用接口和类)javax.servlet.http(定义了采用HTTP通信协议的HttpServlet类),Servlet的生命周期是由servlet容器来控制的,它的生命周期分为三个阶段:
(1)初始化阶段
servlet容器加载servlet类,将.class文件读入内存;
servlet容器创建ServletConfig对象,包含了servlet的初始化配置信息;
servlet容器创建servlet对象
servlet容器调用servlet对象的init方法进行初始化;
初始化的过程通过将当前的servlet类型转化为BeanWrapper类型实例(其目的是方便对应属性注入,属性可以在web.xml中通过<context-param>进行注入,其对应的属性位于其父类FrameworkServlet中,属性注入的步骤:封装及验证初始化参数将当前servlet实例转化为BeanWrapper实例注册相对于Resource(Resource类型的属性将会使用ResourceEditor进行解析)的属性编辑器属性注入servletBean的初始化(主要是对webApplicationContext实例进行填充)
(2)运行阶段
servlet接受请求时,针对请求创建servletRequest,servletResponse,调用service方法(通过servletRequest获得请求信息,根据servletResponse生成相应结果),最后销毁servletRequest,servletResponse两个对象
servlet被设计为请求驱动,请求会被封装为HttpServletRequest对象,然后传给对应服务方法。请求的类型一共有八中(常用的:GET,POST,PUT,DELETE,不太常用的:HEAD,TRACE,CONNECT.OPTIONS),在HttpServlet类中分别提供了对应的服务方法例如doDelete(),例外的是CONNECT并没有提供对应方法
(3)销毁阶段
调用servlet容器的destory方法之后再销毁servlet对象,我们可以将一些servlet占用的资源的释放放在destroy方法中实现

webApplicationContext及其他属性的初始化

由于在ContextLoaderListener和servlet的初始化中都出现了initWebApplicationContext方法,但是ContextLoaderListener中出现的是public WebApplicationContext initWebApplicationContext(ServletContext)该方法位于ContextLoader类中,servlet的方法则为 protected WebApplicationContext initWebApplicationContext() 该方法位于FrameworkServlet类中,下面来介绍后者的逻辑
1.寻找或创建对应的WebApplicationContext实例,该逻辑为

if(WebApplicationContext存在(仅可能通过构造器注入)){
    则通过构造函数的注入进行初始化webApplicationContext;
    如果webApplication没有初始化Spring环境,那么进行初始化Spring环境包括加载配置文件;
}else if(WebApplicationContext仍不存在){
    通过contextAttribute进行初始化webApplicationContext;
}else if(WebApplicationContext仍不存在){
    通过反射的方式重新创建WebApplicationContext实例进行初始化,之后初始化Spring环境包括加载配置文件;
if(如果没有刷新Spring中可能会用到的全局变量){
    (文件上传解析器,只允许一个实例)初始化并添加MultipartResolver(MultipartResolver主要用于文件上传)至DispatcherServlet ;
    /*一般配置如<bean id = "xxx" class="org.Springframework.web.multipart.commons.CommonsMultipartResolver">*/

    (本地化解析器,只允许一个实例)初始化LocaleResolver(国际化配置);
    /*第一种配置基于URL参数
    配置如<bean id="XXX" class="org.Springframework.web.servlet.i8ln.AcceptHeaderLocaleResolver">
    配合<a href="?locale=zh_CN">控制使用国际化化参数
    第二种配置基于session,如果会话属性不存在,那么通过accept-language HTTP头部确定默认区域
    第三种配置基于Cookie,这种策略常用于应用不支持会话或者状态必须保持在客户端的情况
    配置如<bean id="XXX" class="org.Springframework.web.servlet.i8ln.CookieLocaleResolver">*/

    (主题解析器,只允许一个实例)初始化ThemeResolver;
    /*
    org.Springframework.ui.context.ThemeSource是Spring中主题资源的接口,实现存放主题信息资源的类都应该实现该接口
    通过配置
    <bean id="xxx" class="org.Springframework.ui.context.support.ResourceBundleThemeSource">
    其中ResourceBundleThemeSource就是实现了ThemeSource接口
        <property name="basenamePrefix" value="com.test">
        配置在com.test路径下查找资源文件
    </bean>
    ThemeSource配置了主题资源,不同用户的不同资源则由主题解析器定义。
    由org.Springframework.web.servlet.ThemeResolver作为主题解析器的接口
    该接口有三个实现类:
    第一个是FixedThemeResolver用于设置固定主题
    第二个是CookieThemeResolver效果是将主题以cookie的形式放在客户端的机器上
    第三个是SessionThemeResolver效果是将主题保存在HTTP Session中
    第四个是AbstractThemeResolver(是CookieThemeResolver和SessionThemeResolver的父类),用户可以继承它来实现自己的主题解析器
    如果需要根据用户请求改变主题,那么Spring提供了一个已经实现的拦截器ThemeChangeInterceptor
    主题拦截器的配置如下:
    <bean id="XXX" class="org.Springframework.web.servlet.theme.ThemeChangeInterceptor">
        <property name="paramName" value="themeName"></property>
    </bean>
    当然还需要在handlerMapping中添加该拦截器
    <property name="interceptors">
        <list>
            <ref local="themeChangeInterceptor">
        </list>
    </property>
    */

    (处理器映射器,允许多个实例)初始化HandlerMappings;
    /*
        当客户端发出Request时DispatchServlet会将Request提交给HandlerMapping,
        然后HandlerMapping根据WebApplicationContext的配置回传给DispatcherServlet相应的Controller。
        (当然调用时会按照优先级进行排序,优先级高的优先调用)
        默认情况下SpringMVC会加载当前所有实现了HandlerMapping接口的bean,如果只希望加载指定的bean,那么可以如下配置:
        <init-param>
            <param-name>detectAllHandlerMappings</param-name>
            <param-value>false</param-value>
        </init-param>
        之后将按照Dispatcher.properties中所定义的规则来默认加载
    */

    (处理器适配器,允许多个实例)初始化HandlerAdapters;
    /*
    Spring中有三个默认的适配器,如果没有在配置文件中定义自己的适配器,那么Spring会默认加载这三个适配器
    当servlet通过处理器映射得到处理器之后,就会轮询处理器适配器模块,查找能够处理当前HTTP请求的处理器适配器的实现。下面介绍这三种默认的适配器:
    1.HTTP请求处理器适配器
    2.简单控制器处理器适配器
    3.注解方法处理器适配器
    */

    (处理器异常解析器,允许多个实例)初始化HandlerExceptionResolvers;
    /*
    需要实现HandlerExceptionResolver接口,该接口只有一个方法,该方法返回ModelAndView对象。
    如果实现类的该方法返回了null那么Spring会继续寻找其他实现了该接口的bean,直到返回一个ModelAndView对象。
    该类必须声明到Spring的applicationContext.xml中进行管理:
    <bean id="xxx" class="实现类"/>
    */

    (视图名称解析器,只允许一个实例)初始化RequestToViewNameTranslator;
    /*
    用于处理没有返回View对象或者逻辑视图名称,并且该方法中没有直接在response的输出流中写数据时,提供逻辑视图名称。
    Spring提供了一个默认的实现DefaultRequestToViewNameTranslator
    */

    (视图解析器,允许多个实例)初始化ViewResolvers;
    /*
    配置方法(于applicationContext.xml):
    <bean class="org.Springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="xxx"/>
        ...
    </bean>
    */

    (映射个管理器)初始化FlashMapManager;
    /*
    Flashattributes提供了一个请求存储属性,在重定向时非常有用(在重定向之前暂存,以便继续使用)
    FlashMap用于保持flashattributes;
    FlashMapManager用于存储,检索,管理FlashMap实例
    */
}

DispatcherServlet的处理逻辑

虽然HTTP1.1报文头一共有八种,常用的:GET、POST、PUT、DELETE,不太常用的:HEAD、TRACE、CONNECT、OPTIONS,但是实际上FrameworkServlet并没有实现CONNECT和HEAD。他们分别对应着doXXX()方法。
对于不同的方法,Spring统一的将他们引入到processRequest方法中,该方法完成了以下操作:
(1)提取当前线程的LocaleContext和RequestAttribute以便在当前请求后还能够恢复
(2)根据当前request创建LocaleContext和RequestAttribute,并绑定到当前线程
(3)委托doService方法进一步处理
仍然是准备工作,将webApplicationContext,localeResolver等属性注入request中,然后继续由doDispatch方法进行处理,doDispatch方法的处理逻辑为:
第一步:将MultipartContent类的request转化为MultipartHttpServletRequest类型的request
第二步:根据request信息寻找对应的Handler,Spring在request到达后,通过遍历所有的HandlerMapping,如果只是String那么通过查找对应的bean找到controller,否则通过URL找出对应的controller(考虑直接匹配和通配符),之后通过getHandlerExecutionChain进行Handler封装,将拦截器加入执行链,这样方便扩展和拦截,也是AOP的重要基础,其中AbstractUrlHandlerMapping与AbstractController的关系如下所示:
AbstractUrlHandlerMapping与AbstractController的关系
第三步:若如果没有找到对应的Handler则通过response向用户报错
第四步:根据当前的Handler寻找对应的HandlerAdapter(遍历寻找)
第五步:缓存处理,根据Last-Modified缓存机制(第一次成功请求后如果在第二次请求之间内容没有改变,那么返回304状态码),Controller需要实现LastModified接口
第六步:HandlerInterceptor的处理,处理拦截必须实现HandlerInterceptor接口
第七步:逻辑处理,通过适配器中转调用Handler(处理用户定义的逻辑)并返回视图
第八步:异常视图处理,由HandlerExceptionResolver的resolveException完成
第九步:根据视图跳转页面,主要进行了一下几个方面的处理:基于效率的考虑,提供了对缓存的支持;提供了对redirect:xx和forward:xx的支持;添加了前缀以及后缀,并向View中加入了必需的属性设置。在页面跳转以前,还需要处理的就是将需要用到的属性放入request中。
(4)请求处理结束后恢复线程到原始状态
(5)发布事件通知

  • 7
    点赞
  • 66
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值