探索SpringMVC-DispatcherServlet

news2024/12/28 20:57:26

前言

在《探索SpringMVC-web上下文》中,我们介绍了DispatcherServlet的上下文的初始化。然后为了让大家对DispatcherServlet的各个组件有所了解,我们花了很多的时间来介绍各大组件。现在我们来看看DispatcherServlet是如何使用这些组件完成功能的。

DispatcherSerlvet的结构

DispatcherServlet
图释:黄色部分是javax.servlet包的,绿色部分是org.springframework的。而红色是DispatcherServlet的九大组件。

结构分析

javax的设计

  • GenericServlet:通用的Servlet
    这是个抽象类,意在提供一个通用的、与协议无关的Servlet基类。他实现了两个接口:Servlet和ServletConfig。如果你非要说设计在一个接口里面不行吗?可以。但是我们总是希望最求一种高内聚低耦合的设计,希望减少软件系统的维护成本。从设计原则上讲,这里是接口隔离原则。只向客户端提供其需要的行为。
  • HttpServlet:Http协议Servlet
    这是为了Http协议而拓展的Servlet。从类结构上看,他实现了Servlet接口的service方法,并且将范围权限缩小到protected。该方法会将ServletRequest、ServletResponse转成HttpServletRequest、HttpServletResponse。然后会调用重载方法service,该方法就是专门为http扩展的POST、GET、PUT等进行支持,提供对应的doPost、doGet、doPut等方法。

Spring的扩展

  • HttpServletBean
    为this(当前HttpServlet对象)提供属性绑定,基于ServletConfig获取属性值。其核心能力来自BeanWrapImpl,这个在之前讲RequestMappingHandlerAdapter参数解析时也提到过,他是Spring重要的底层支撑组件。在init()方法中执行该操作。并扩展出来initServletBean()让子类执行自己的初始化。

  • FrameworkServlet
    Spring的抽象框架Servlet类,为架设SpringMVC处理框架做准备。他会继承Spring上下文,从而为子类提供从上下文获取各种对象的能力。
    他干了两个重要的工作

    1. initServletBean()初始化了上下文、并且调用模板方法onRefresh(ApplicationContext context)
    2. 将所有请求统一调度到processRequest方法,并调用抽象doService处理请求。因为只有统一了入口,才有可能提供统一的处理能力。而processRequest方法会维护localeContext、RequestAttributes,同时还会发布ServletRequestHandledEvent时间。
  • DispatcherServlet
    SpringMVC的核心,意为将请求分发到处理的处理器进行处理。在onRefresh方法中从上下文获取到九大组件,从而真正使得DispatcherServlet具备请求处理条件。
    实现doService方法,为了便于Handler和View使用框架组件,将ApplicationContext、ThemeResolver、LocaleResolver设置为request的Attribuite。然后调用到关键的doDispatch方法处理请求。后面会重点讲该方法。

DispacherServlet的初始化

这里复习一下,之前的内容:

DispatcherServlet是基于Servlet的声明周期方法init来进行初始化的。初始时,会刷新上下文,并且会通过ApplicationContext初始化DispatcherServlet所依赖的九大组件。

初始化九大组件

在《探索SpringMVC-九大组件》中,我们知道onRefresh方法会调用initStrategies,初始化策略。而该方法就会初始化DispatcherServlet的各个组件。

从设计模式看,因为每个组件都有各种各样的实现,因此使用的策略模式。那么初始化组件,就是初始化策略。这应该也是其方法命名的缘由。

其初始化也比较简单,就是从ApplicationContext中直接获取对应的组件。如果ApplicationContext中没有,则使用默认的。这个默认的就配置在DispatcherServlet.properties中。

组件的声明

最常用的配置就是@EnableWebMvc。他会引入DelegatingWebMvcConfiguration配置,就是这个类声明了各个组件。SpringMVC还提供了WebMvcConfigurer接口,便于大家进行定制。

这些配置都是在上下文刷新时,被加载到容器中的。

DispatcherServlet的请求处理

前面讲结构的时候,我们提到doDispatch就是处理请求的关键方法。现在我们来看看他是怎么处理请求的。

先说明,我们不会分析异步请求。

这里我将该方法的核心逻辑代码提炼出来

try {
	// 检查请求是否为multipart request。是则通过MultipartResolver进行包装
	processedRequest = checkMultipart(request);
	// 1. 确定处理当前请求的Handler
	mappedHandler = getHandler(processedRequest);
	// 2. 确定handler的适配器
	HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
	// 3.1 调用拦截器的前置方法
	if (!mappedHandler.applyPreHandle(processedRequest, response)) {
		return;
	}

	// 3.2 调用handler处理请求
	mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
	// 3.3 调用拦截器的后置方法
	mappedHandler.applyPostHandle(processedRequest, response, mv);
	// 如果mv为空,则调用RequestToViewNameTranslator获取默认的viewName
	applyDefaultViewName(processedRequest, mv);
	// 4. 处理分发后的结果:异常、响应视图。
	processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
	// 确保拦截器的afterCompletion方法被调用
	triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
	// 确保拦截器的afterCompletion方法被调用
	triggerAfterCompletion(processedRequest, response, mappedHandler,
			new NestedServletException("Handler processing failed", err));
}

下面我们来具体分析这每个步骤的细节:

    1. 确定处理当前请求的Handler
      这里会遍历this.handlerMappings属性,只要返回不为空,则使用该HandlerMapping
    1. 确定handler的适配器
      通过HandlerMapping拿到Handler,再遍历this.handlerAdapters,只要找到support则返回该HandlerAdapter
  • 3.1 调用拦截器的前置方法
    遍历this.interceptorList,调用HandlerInterceptor#preHandle方法。如果该方法返回false,还需要调用HandlerInterceptor#afterCompletion。因为该方法返回false会阻止请求处理。而HandlerInterceptor#afterCompletion不管请求正常出完成还是异常退出,都需要被调用。
  • 3.2 调用handler处理请求
    会调用HandlerAdapter#handle方法处理请求
  • 3.3 调用拦截器的后置方法
    遍历this.interceptorList,调用HandlerInterceptor#postHandle方法。
    1. 处理分发后的结果:异常、响应视图。
      由于异常处理器也可能返回ModelAndView,因此先处理异常。异常不为空,调用processHandlerException处理异常。该方法会遍历异常处理器来处理异常。
      视图不为null,则render方法会遍历视图解析器解析视图,不为空则返回view,并调用View#render方法响应页面。

有上面的步骤,我们可以看到,一个常规的请求是如何被处理的。以及HandlerMapping、HandlerAdapter、HandlerExceptionResolver、ViewResolver这几个关键组件是如何被调用串联的。他们就是流水线式的干活。这里没有提到另外的几个组件,原因是为了重点给大家分析DispatcherServlet的核心处理逻辑。这里给大家稍微提一下:

  • LocaleResolver负责解析请求的本地语言。在DispatcherServlet中会调用request.setAttribute方法放到request中。便于后续Handler等等组件处理国际化时使用。
  • ThemeResolver只是负责解析主题名称。真正干活的是ThemeSource。说的简单点,就是你在视图技术(例如JSP、FreeMark)页面设置的样式通过占位符引用model中对应的样式名称取值。想了解更多的同学这边请:Spring MVC更多家族成员–主题(Theme)与ThemeResolver
  • MultipartResolver会对Request进行封装。例如StandardServletMultipartResolver会将request封装成StandardMultipartHttpServletRequest。
  • RequestToViewNameTranslator在HandlerAdapter没有返回ModelAndView时,会通过他获取默认的viewName。
  • FlashMapManager则与重定向有关,在处理请求之前,会通过他获取/保存FlashMap(重定向参数)。

总结

  1. DispatcherServlet的结构分为两个层次。一个是javax的,另一个则是spring的。
  2. DispatcherServlet的初始化基于Servlet的生命周期函数init方法初始化的。在该函数中完成WebApplicationContext的初始化,并在上下文refresh之后,初始化DispatcherServlet的相关组件。
  3. DispatcherServlet的处理过程大致分为三大步骤
    • 初始化请求:包括封装请求、处理重定向参数
    • 处理请求:从HandlerMapping找到Handler,再通过Handler找到HandlerAdapter,调用HandlerAdapter执行Handler处理逻辑。
    • 渲染视图响应:调用视图解析器获得视图对象。调用View.render方法响应视图。
    • 分支逻辑:异常处理、拦截器调用。

专栏总结

  1. DispatcherServlet的重要武器是WebApplicationContext。各种组件包括处理器都是在初始化时从WebApplicationContext获取到响应的对象的。例如:RequestMappingHandlerMapping在初始化的过程中就从容器中遍历所有bean寻找@Controller/@RequestMapping。因此在DispatcherServlet正常对外提供服务时,核心处理逻辑都不需要再通过上下文获取bean了。
  2. SpringMVC的工作流程,就借用百度百科的图了
    在这里插入图片描述

后记

终于把专栏完成了,这是第一个完整完成的专栏,如果有不对的地方,欢迎大家多提意见,一起探讨。《探索SpringMVC》


祝大家新年快乐。

第一篇:
探索SpringMVC-web上下文
上一篇:
探索SpringMVC-组件之ViewResolver

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/175017.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【前端杂货铺】一个普通人在CSDN创作的一周年

个人简介 👀个人主页: 前端杂货铺 🙋‍♂️学习方向: 主攻前端方向,也会涉及到服务端 📃个人状态: 在校大学生一枚,已拿多个前端 offer(秋招) 🚀未…

Python---列表和元组

专栏:python 个人主页:HaiFan. 专栏简介:本专栏主要更新一些python的基础知识,也会实现一些小游戏和通讯录,学时管理系统之类的,有兴趣的朋友可以关注一下。 列表和元组前言列表的的概念列表的创建访问下标…

【微服务】Eureka注册中心

本系列介绍的是Spring Cloud中涉及的知识点,如有错误欢迎指出~ 一.引子 假如我们的服务提供者user-service部署了多个实例,如图: 大家思考几个问题: 问题一:order-service在发起远程调用的时候,该如何得知…

Linux——一文彻底了解进程id和线程id的关系(什么是pid、tgid、lwp、pthread_t)

目录 一.内核层面:pid & tgid 二.函数调用层面:getpid & gettid & pthread_self 三.用户层面:PID & LWP(TID) 四.总结 一.内核层面:pid & tgid 首先,我们要清楚&#…

【运筹优化】凸多面体重叠判断算法:GJK 算法详解 C++代码实现二维情形的凸多边形重叠判断

文章目录一、GJK 算法简介二、前置知识2.1 二维向量的点乘和叉乘2.2 三维向量叉乘2.3 凸多边形2.4 闵可夫斯基差2.5 单纯形2.6 Support 函数三、GJK 算法讲解3.1 熟悉 GJK 算法流程3.1.1 多边形重叠的情形3.1.2 多边形不重叠的情形3.2 总结 GJK 算法步骤3.3 讲解 GJK 算法细节3…

HTML5(下)

目录 表格标签 表格的主要作用 表头单元格标签 表格结构标签 合并单元格 列表标签 无序列表 有序列表 自定义列表 表单 表单域 表单控件(表单元素) 表单元素 label标签 select下拉列表 textarea文本域元素 案例-注册页面 表格标签 表格的主…

面试官: 你们生产环境的JVM怎么设置的?

前言 这篇文章,给大家聊一个生产环境的实践经验:线上系统部署的时候,JVM堆内存大小是越大越好吗? 先说明白一个前提,本文主要讨论的是Kafka和Elasticsearch两种分布式系统的线上部署情况,不是普通的Java应…

【附代码】十大经典排序算法

常见的内部排序算法有:插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。用一张图概括:名词解释:n:数据规模。k:“桶”的个数。In-place:占用常数内存,不占用…

TryHackMe-Docker_Rodeo

The Docker Rodeo 在此引导式展示中了解各种 Docker 漏洞。 以下内容均来自TryHackMe 前提设置 /etc/docker/daemon.json {"insecure-registries" : ["docker-rodeo.thm:5000","docker-rodeo.thm:7000"] }Docker注册表 在我们开始利用 Docke…

【Java开发】Spring Cloud 05 :远程服务调用Openfeign 替代 WebClient

在前边章节中,我们借助 Nacos 的服务发现能力,使用 WebClient 实现了服务间调用。从功能层面上来讲,我们已经完美地实现了微服务架构下的远程服务调用,但是从易用性的角度来看,这种实现方式似乎对开发人员并不怎么友好…

软件测试复习10:测试文档

专栏:《软件测试》 个性签:顺境不惰,逆境不馁,以心制境,万事可成。——曾国藩 测试大纲:招标用,总体策略,对软件的了解,测试人员,资质等。 测试计划&#…

将Bean创建到Spring容器,从Spring容器拿出Bean

目录一、XML文件中,将Bean创建到Spring容器1. 基本类型注册2. 类装配3. 有参构造方法装配4. 扩展注入5. Bean的作用域6. Bean的其他配置二、配置类中,将Bean创建到Spring容器1. 在mapper、service、controller中创建,等着被componentScan扫描…

C++ | 关于STL中的空间配置器 | 源码剖析

文章目录为什么需要空间配置器一级空间配置器二级空间配置器内存池解析refill 填充内存池chunk_alloc 申请堆空间deallocate 资源的归还空间配置器的再次封装空间配置器与容器的结合我们知道在C和C中都有关于内存管理的问题,C语言用malloc和free这两个函数体现内存管…

ClassLoader-在spring中的应用

背景标题起的挺大,忽悠人的。其实是我跟着视频学习手写模拟spring底层原理中遇到的问题,关于classLoader的几行代码,不知道是什么意思,所以特地来记下笔记。关于ClassLoader我好像在遥远的几年前看深入理解虚拟机时看到过&#xf…

Datawhale 202301 设计模式 | 第二章 人工智能 现代方法 智能体

智能体和环境 理性智能体 (rational agent) 需要为取得最佳结果或在存在不确定性时取得最佳期望结果而采取行动。 任何通过传感器(sensor) 感知 环境(environment) 并通过 执行器(actuator) 作用于该环境 的事物都可以被视为 智能体(agent) 。 行为 理性智能体 (rational ag…

Linux常用命令——systemctl命令

在线Linux命令查询工具(http://www.lzltool.com/LinuxCommand) systemctl 系统服务管理器指令 补充说明 systemctl命令是系统服务管理器指令,它实际上将 service 和 chkconfig 这两个命令组合到一起。 任务旧指令新指令使某服务自动启动chkconfig --level 3 ht…

属性值的计算过程 css样式显示的计算过程 页面的渲染流程

目录属性值的计算过程属性值计算过程简介通过例子来理解:详细解释:方法例子属性值的计算过程 一个元素一个元素依次渲染,顺序按照页面文档的树形目录结构进行 渲染每个元素的前提条件:该元素的所有CSS属性必须有值 一个元素&am…

数学魔法结局:muldiv

介绍了一些棘手的数学魔法,但我一直没有抽出时间说出妙语。目标是计算 同时正确处理溢出。我们的秘密武器是 EVM 的mulmod指令。这条指令完全符合我们的要求,只是它返回的是余数而不是商。那么我们的策略是什么? 计算 512 位乘积一种⋅b使用…

【数据结构】6.5 图的遍历

文章目录遍历定义深度优先搜索(DFS)算法步骤邻接矩阵上的遍历邻接矩阵深度优先算法DFS算法效率分析广度优先搜索(BFS)邻接表的广度优先算法BFS算法效率分析DFS与BFS算法效率比较遍历定义 和树的遍历类似,图的遍历也是从图中的某一个顶点出发,按照某种方法…

UPS BP650CH实现nas自动关机

家里有个自己拼凑的nas需要防止断电不正常关机,因此购买了施耐德后背式BP650CH,之所以选这款是因为带了串口,串口终究还是很方便的东西。不管linux还是window还是其他系统都能够使用,通过串口直接获得ups的信息,就不需…