DispatcherServlet代码分析及运行过程

1    首先该类有一静态语块,用以加载缺省策略。

static {

ClassPathResource resource =new ClassPathResource(DEFAULT_STRATEGIES_PATH,DispatcherServlet.class);

defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);

}

缺省的策略文件为当前包下的DispatcherServlet.properties,主要包括

LocaleResolver(本地化解析器,AcceptHeaderLocaleResolver)

ThemeResolver(主题解析器,FixedThemeResolver)

HandlerMapping(处理器映射,BeanNameUrlHandlerMapping)

HandlerAdapter(控制适配器,多个)

ViewResolver(视图解析器,InternalResourceViewResolver)

RequestToViewNameTranslator(请求到视图名的翻译器,DefaultRequestToViewNameTranslator)

2    通过继承关系调用HttpServletBean类的init()初始化方法

在该方法体内,首先将这个servlet视为一个bean对其包装并将配置文件中当前servlet下的初始参数值赋倒这个servlet中。然后调用子类(FrameworkServlet)的initServletBean(),而子类通过模板模式再调用其子类(DispatcherServlet)的initFrameworkServlet()方法。在initServletBean()方法中主要代码片段如下:

this.webApplicationContext = initWebApplicationContext();

initFrameworkServlet();

而在initWebApplicationContext()中,它首先是从ServletContext中获得由ContextLoader类设置进的key为WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE属性的值(即Spring容器的实例),然后以实例为parent创建一个新的容器。这也是为什么说一个DispatcherServlet就对应一个WebApplicationContext(每个DispatcherServlet都有各自的上下文)。最后将该WebApplicationContext绑定到到ServletContext上,如果允许发布话。

由此可以看出:

WebApplicationContextUtils.getWebApplicationContext(servletContext)与

servletContext.getAttribute(SERVLET_CONTEXT_PREFIX+ getServletName())之间的主要区别在于:后者包括了该servlet的所有配置信息而前者是全局性的没有这部分信息

3    类DispatcherServlet中initFrameworkServlet()方法初始化工作

initMultipartResolver();

initLocaleResolver();

initThemeResolver();

initHandlerMappings();

initHandlerAdapters();

initHandlerExceptionResolvers();

initRequestToViewNameTranslator();

initViewResolvers();

如果在配置文件没有设置将缺省的策略文件DispatcherServlet.properties。由此可见,因为DispatcherServlet是单例的,所以设置的信息是全局的也就是所有的解析器不能动态的变化。

4    当调用类FrameworkServlet中的doGet()/doPost()/doPut()/doDelete()方法时

以上方法均调用相同方法processRequest(request, response)。注意:Spring放弃了对HttpServlet中service()方法的覆盖而用具体的操作方法取代。

5    调用类FrameworkServlet中的processRequest(request, response)方法

try {

doService(request, response);

}

finally {

if (isPublishEvents()) {

this.webApplicationContext.publishEvent(

new ServletRequestHandledEvent(this,

request.getRequestURI(), request.getRemoteAddr(),

request.getMethod(), getServletConfig().getServletName(),

WebUtils.getSessionId(request), getUsernameForRequest(request),

processingTime, failureCause));

}

}

通过代码摘要可以看出该类有一个webApplicationContext引用,除了执行doService()这个核心方法外,每次都会发布一个ServletRequestHandledEvent事件如果publishEvents为true,其值可以在web.xml文件中增加添加context参数,或servlet初始化参数(

<init-param>

<param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/application.xml</param-value>

</init-param>)。

DispatcherServlet初始化参数

参数

描述

contextClass

实现WebApplicationContext接口的类,当前的servlet用它来创建上下文。如果这个参数没有指定,默认使用XmlWebApplicationContext。

contextConfigLocation

传给上下文实例(由contextClass指定)的字符串,用来指定上下文的位置。这个字符串可以被分成多个字符串(使用逗号作为分隔符)来支持多个上下文(在多上下文的情况下,如果同一个bean被定义两次,后面一个优先)。

namespace

WebApplicationContext命名空间。默认值是[server-name]-servlet。

publishContext

我们发布的context是作为ServletContext的一个属性吗?默认值为true,属性名为SERVLET_CONTEXT_PREFIX+ getServletName()

publishEvents

在执行每个请求后是否发布一个ServletRequestHandledEvent事件?默认值为true

以上参数的初始加载是在HttpServletBean类的init()中,而所有DispatcherServlet的解析器是在initFrameworkServlet()方法中。

由此可见,DispatcherServlet的初始参数加载来源可分为两部分,

5.1   在web.xml文件对于该servlet配置的<init-param>初始参数中

5.2   通过Spring的IOC容器实例化后,通过该类的初始化方法,将容器内的实例一一引用

6    调用类DispatcherServlet中的doService(request, response)方法

暴露在request属性中DispatchServlet的一些特性,并且调用doDispatch(request, response)方法执行真正的分发动作。

request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());

request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE,this.localeResolver);

request.setAttribute(THEME_RESOLVER_ATTRIBUTE,this.themeResolver);

request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

try {

doDispatch(request, response);

}

7    调用类DispatcherServlet中的doDispatch(request, response)方法

7.1    在区域上下文持有者(LocaleContextHolder)中当前线程下的值做一次替换。暴露当前localeResolver与request中的Locale

LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();

LocaleContextHolder.setLocaleContext(new LocaleContext() {

public Locale getLocale() {

returnlocaleResolver.resolveLocale(request);

}});

由源码可见,将当前线程下的LocaleContext取出,再从当前区域解析器(LocaleResolver)中得到Locale前创建为LocaleContext后再赋到LocaleContextHolder中

7.2   在请求上下文持有者(RequestContextHolder)中当前线程下的值做一次替换。暴露当前RequestAttributes到当前线程中

RequestAttributes previousRequestAttributes = RequestContextHolder.getRequestAttributes();

ServletRequestAttributes requestAttributes =new ServletRequestAttributes(request);

RequestContextHolder.setRequestAttributes(requestAttributes);

在此注意,ServletRequestAttributes类只能获得session或request有属性,而无法获得request的参数(parameter)

7.3   转换request为MultipartHttpServletRequest类型的实例,如果不是就简单的返回

protected HttpServletRequest checkMultipart(HttpServletRequest request) {

if (this.multipartResolver != null &&this.multipartResolver.isMultipart(request)) {

if (requestinstanceof MultipartHttpServletRequest) {

}

else

returnthis.multipartResolver.resolveMultipart(request);

}

return request;

}

由代码可以看出,如果要支持文件上传要有两个前提

1.在应用的配置文件中添加MultipartResolver(分段文件解析器),目前Spring支持两种上传文件的开源项目Commons FileUpload(http://jakarta.apache.org/commons/fileupload)和COS FileUpload(http://www.servlets.com/cos)

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>
<bean id="multipartResolver" class="org.springframework.web.multipart.cos.CosMultipartResolver"/>

注意:Bean的id必须为’ multipartResolver’, DispatcherServlet中的成员属性this.multipartResolver是在初始化时与容器中的实例引用在一起的。

2. 通过multipartResolver.isMultipart(request)判断request的ContextType是否是’ multipart/form-data’。所以表单格式应为

        <form method="post" action="upload.form" enctype="multipart/form-data">
            <input type="file" name="file"/><input type="submit"/>
        这个元素的名字(“file”)和服务器端处理这个表单的bean(在下面将会提到)中类型为byte[]的属性名相同。在这个表单里我们也声明了编码参数(enctype="multipart/form-data")以便让浏览器知道如何对这个文件上传表单进行编码

最后由相应的MultipartResolver(分段文件解析器)将request封装为与解析器对应的MultipartHttpServletRequest实例并返回

7.4   根据当前请求URL为其找到指定的处理器(handler)与栏截器(HandlerInterceptor),并将它们封装为HandlerExecutionChain实例。

HandlerExecutionChain mappedHandler = getHandler(processedRequest,false);

在此过程中分为如下几个步骤

1.  循环所有已注册的HandlerMapping(处理器映射),对于不同的处理器映射对其映射的处理方式不同。BeanNameUrlHandlerMapping只要在配置文件中bean元素的name属性以/开头它都认为是一个映射如<bean name=”/editaccount”>。而SimpleUrlHandlerMapping只关注自身定义的<property name=”mappings”>下的那些映射,而且支持Ant风格的定义

2.  通过客户请求的URL与处理器映映射的键值做匹配(也可以使用Ant风格匹配,参见AbstractUrlHandlerMapping.lookupHandler()方法),如果满足匹配条件,就返回与其对应的处理器(Handler),一般情况下,都是Controller(控制器)接口子孙类的实例。

3.  根据匹配上的处理器映射取得拦截器数组,并与找到的处理器一同作为构建函数的参数创建出HandlerExecutionChain实例

7.5   执行所有拦截器的前置方法

HandlerInterceptor接口提供了如下三个方法:

1.  preHandle(…)前置方法,在处理器执行前调用,当返回false时,DispatcherServlet认为该拦截器已经处理完了请求,而不再继续执行执行链中的其它拦截器和处理器

2.  postHandle(…)后置方法,在处理器执行后调用

3.  afterCompletion(…)完成前置方法,在整个请求完后调用。注意如果第n个拦截器的preHandle方法返回的是false则只会调用第n个拦截器之前所有该方法,而n++不会被调用

另:Spring还提供了一个adapter类HandlerInterceptorAdapter让用户更方便的扩展HandlerInterceptor接口

7.6   获得与当前处理器相匹配的HandlerAdapter(处理器适配器),

protected HandlerAdaptergetHandlerAdapter(Object handler) throws ServletException {

Iterator it = this.handlerAdapters.iterator();

while (it.hasNext()) {

HandlerAdapter ha = (HandlerAdapter) it.next();

if (ha.supports(handler)) {

return ha;

}

}

循环所有已注册的HandlerAdapter(处理器适配器),如果该适配器支持ha.supports(handler)当前处理器,就返回。由此可见,Spring通过HandlerAdapter使处理器解耦,实处理器(Handler)不只是仅仅局限于控制器(Controller)这一种形式。目前Spring可以支持,ServletHttpRequestHandlerThrowawayControllerController,从这一点上看Spring已为客户留出了很大空间作为扩展的口子。

7.7   根据处理器适配器调用处理器的指定方法,执行真正的处理操作。如Controller(控制器)接口调用handleRequest()、ThrowawayController接口调用execute()、Servlet接口调用service()….但无论调用那种自理器,HandlerAdapter中的handle()方法都要求返回ModelAndView的一个实例

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

在Spring中最常用的处理器是以Controller接口下的所有子孙类控制器,继承结构如图

在这些控制器中又以SimpleFormController控制器最为常用,下图为该类的完整执行过程

7.8   执行所有拦截器的后置方法

7.9   根据处理器(Handle)返回的ModelAndView对象,找到或创建View对象,并调用View对象的render()方法

render(mv, processedRequest, response)

在此有几个关键性的概念:

ModelAndView:它是模形(Model,实际上只是一个Map,最终会将模形存放到request的属性中,以Map的key为request属性的键,Map的value为值)与视图(View)的持有者。而View又提供了两种形态,即以View接口为祖先的实例对象,以字符串的视图的名字。该类通过isReference()方法判断是一个字符串的引用还是一个真正的View对象。注意:该类有些奇怪:它将有状态的Model与无状态的View合并到一个类中,这就导致每次请求都到对该类型的实例进行创建,而不无缓存。View对象Spring提供了缓存功能。

ViewResolver(视图解析器):主要提供的功能是从视图名称到实际视图的映射。也就是说只有ModelAndView的isReference()返回true时,才会对视图的名字做解析工作。这使视图解析与真正的视图相分离,这样我们就可以通过不同的方式配置视图,如可以采用properties、xml或是提供URI的前、后缀等形式配置视图。但要注意视图解析器与视图之间还是紧密的关系,Sping在此没实现全部的完全解耦

ViewResolver

描述

AbstractCachingViewResolver

抽象视图解析器实现了对视图的缓存。在视图被投入使用之前,通常需要进行一些准备工作。从它继承的视图解析器将对要解析的视图进行缓存。

InternalResourceViewResolver

作为UrlBasedViewResolver的子类,它支持InternalResourceView(对Servlet和JSP的包装),以及其子类JstlViewTilesView。通过setViewClass方法,可以指定用于该解析器生成视图使用的视图类。

UrlBasedViewResolver

UrlBasedViewResolver实现ViewResolver,将视图名直接解析成对应的URL,不需要显式的映射定义。如果你的视图名和视图资源的名字是一致的,就可使用该解析器,而无需进行映射。

ResourceBundleViewResolver

ResourceBundleViewResolver实现ViewResolver,在一个ResourceBundle中寻找所需bean的定义。这个bundle通常定义在一个位于classpath中的属性文件中。默认的属性文件是views.properties

XmlViewResolver

XmlViewResolver实现ViewResolver,支持XML格式的配置文件。该配置文件必须采用与Spring XML Bean Factory相同的DTD。默认的配置文件是/WEB-INF/views.xml

VelocityViewResolver /FreeMarkerViewResolver

作为UrlBasedViewResolver的子类,它能支持VelocityView(对Velocity模版的包装)和FreeMarkerView以及它们的子类。

View(视图):处理请求的准备工作, 并将该请求提交给某种具体的视图技术。

如,InternalResourceView会通过RequestDispatcher类调用include()或forward()方法

RedirectView会通过HttpServletResponse类调用sendRedirect()方法

AbstractExcelView会产生一个输出流

区别:

1、forward与include区别在于forward本意是让第一个页面处理request,第二个页面处理response.调用第二个页面后,程序还会返回第一个页面继续执行,但是此时再使用response输出已经没有作用了;include服务器端的动态加载,执行完第二个页面的程序后可以回到第一个页面继续输出,即将第二个页面的输出拉到第一个页面中。

2、forward与include共享Request范围内的对象,而redirect则不行,即:如果一个javabean被声明为request范围的话,则被forward到的资源也可以访问这个javabean,而redriect则不行。

3、forward与include基本上都是转发到context内部的资源,而redirect可以重定向到外部的资源,如: req.sendRedriect("http://www.mocuai.com");

DispatcherServlet代码分析及运行过程相关推荐

  1. 嵌入式Linux——分析u-boot运行过程(3):u-boot第三阶段代码

    简介: 本文主要介绍在u-boot-1.1.6中代码的运行过程,以此来了解在u-boot中如何实现引导并启动内核.这里我们主要介绍u-boot第三阶段的代码.而第三阶段的代码主要讲解的是在u-boot ...

  2. host速度 mtk usb_mtk-usb代码分析之枚举过程

    基于mt6750T,Android 7.0,kernel 3.18.35,本文主要简述了USB的枚举过程,主要是从host的角度来看. 一.USB的拓扑结构 简单来说,USB由host和device两 ...

  3. DRM驱动代码分析:开机过程中显示驱动做了什么

    前言: 有些信息是在网上查资料后总结的,没有去追代码验证.如果有说得不对的地方,欢迎提出指正.感谢! 手机启动的大致流程 1.长按开机键 2.执行存储在ROM里(应该是某一个固定地址或是预定义的地址) ...

  4. 模块加载过程代码分析1

    一.概述 模块是作为ELF对象文件存放在文件系统中的,并通过执行insmod程序链接到内核中.对于每个模块,系统都要分配一个包含以下数据结构的内存区. 一个module对象,表示模块名的一个以null ...

  5. kernel 3.10代码分析--KVM相关--虚拟机创建\VCPU创建\虚拟机运行

    分三部分:一是KVM虚拟机创建.二是VCPU创建.三是KVM虚拟机运行 第一部分: 1.基本原理 如之前分析,kvm虚拟机通过对/dev/kvm字符设备的ioctl的System指令KVM_CREAT ...

  6. kernel 3.10代码分析--KVM相关--虚拟机运行

    1.基本原理 KVM虚拟机通过字符设备/dev/kvm的ioctl接口创建和运行,相关原理见之前的文章说明. 虚拟机的运行通过/dev/kvm设备ioctl VCPU接口的KVM_RUN指令实现,在V ...

  7. 海思AI芯片(Hi3519A/3559A)方案学习(十七)开发板上运行yolo3模型的代码分析

    前言 前面的博客系列 已经介绍了如何将caffemodel转换成wk文件,如何将jpg文件转成bgr格式数据以及如何在PC上仿真模型推理等,基于这些基础,本文来结合代码分析如何在板子上推理yolov3 ...

  8. windows server 2012 r2 运行过程中蓝屏 代码0xc000021a

    windows server 2012 r2 运行过程中蓝屏 代码0xc000021a,分析蓝屏错误为ntoskrnl.exe. 解决方法: 补丁安装顺序 1-Windows8.1-kb3021910 ...

  9. HI3861学习笔记(3)——编译构建和代码运行过程

    一.Ninja编译工具简介 在Unix/Linux下通常使用Make/Makefile来控制代码的编译,但是Makefile对于比较大的项目有时候会比较慢,Ninja是Google的一名程序员推出的注 ...

最新文章

  1. Im2Mesh GAN:从一张RGB图像中恢复3D手部网格
  2. 孙立岩 python-basic: 用于学习python基础的课件(五六七八九十)
  3. linux之find命令详解
  4. JDK7 AIO介绍
  5. Java IO Serialization
  6. Kafka 0.9 新消费者API
  7. java上传永久图文素材_Java-微信开发上传永久素材(支持所有文件类型)
  8. 博士仅用2周投中了篇论文,戏耍157家期刊,被Science报道!
  9. 【LGP5161】WD与数列
  10. 企业级CentOS操作系统的磁盘分区
  11. (转)HDOJ 4006 The kth great number(优先队列)
  12. spring--事务原理
  13. java二维数组扫雷,Java 数组 之 二维数组 扫雷实例
  14. 怎么看神经网络过早收敛_遗传算法的收敛性分析
  15. Java 二叉树层次遍历
  16. 房屋租赁管理系统(Java源码+论文)
  17. 数学基础篇 有理数(一)
  18. 如何生成微信小程序码(获取微信小程序码)
  19. debconf_Starbound的开源游戏开发,DebConf上的SteamOS等
  20. 无套路!最新官宣的1000+微信红包封面,领取入口戳→

热门文章

  1. Python学习笔记--程序控制结构
  2. Python入门:正则表达式
  3. wxWidgets:缓冲区类
  4. boost::phoenix::find相关的测试程序
  5. boost::mpi::wait_all相关用法的测试程序
  6. boost::math::pow相关用法的测试程序
  7. boost::is_readable_iterator用法的测试程序
  8. boost::describe模块实现==重载的测试程序
  9. GDCM:gdcm::TableReader的测试程序
  10. Boost:双图和boost assign的测试程序