当前位置: 首页 > news >正文

DispatcherServlet 源码分析

一.DispatcherServlet 源码分析

本文仅了解源码内容即可。

1.观察我们的服务启动⽇志:
当Tomcat启动之后, 有⼀个核⼼的类DispatcherServlet, 它来控制程序的执⾏顺序.所有请求都会先进到DispatcherServlet,执⾏doDispatch 调度⽅法. 如果有拦截器, 会先执⾏拦截器 preHandle() ⽅法的代码, 如果 preHandle() 返回true, 继续访问controller中的⽅法. controller当中的⽅法执⾏完毕后,再回过来执⾏ postHandle() afterCompletion() ,返回给DispatcherServlet, 最终给浏览器响应数据.

1.1初始化(了解) 

DispatcherServlet的初始化⽅法 init() 在其⽗类 HttpServletBean 中实现的
主要作⽤是加载 web.xml 中 DispatcherServlet 的 配置, 并调⽤⼦类的初始化
web.xml是web项⽬的配置⽂件,⼀般的web⼯程都会⽤到web.xml来Listener,Filter,Servlet等, Spring框架从3.1版本开始⽀持Servlet3.0, DispatcherServlet, 实现不再使⽤web.xml

init() 具体代码如下:

 @Overridepublic final void init() throws ServletException {try {// ServletConfigPropertyValues 是静态内部类,使⽤ ServletConfig 获取web.xml 中配置的参数PropertyValues pvs = newServletConfigPropertyValues(getServletConfig(), this.requiredProperties);// 使⽤ BeanWrapper 来构造 DispatcherServletBeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);ResourceLoader resourceLoader = newServletContextResourceLoader(getServletContext());bw.registerCustomEditor(Resource.class, newResourceEditor(resourceLoader, getEnvironment()));initBeanWrapper(bw);bw.setPropertyValues(pvs, true);} catch (BeansException ex) {}// 让⼦类实现的⽅法,这种在⽗类定义在⼦类实现的⽅式叫做模版⽅法模式initServletBean();}
HttpServletBean init() 中调⽤了 initServletBean() , 它是在
FrameworkServlet 类中实现的, 主要作⽤是建⽴ WebApplicationContext 容器(有时也称上下⽂), 并加载 SpringMVC 配置⽂件中定义的 Bean到该容器中, 最后将该容器添加到 ServletContext 中
initServletBean() 的具体代码:
    /*** Overridden method of {@link HttpServletBean}, invoked after any bean properties* have been set. Creates this servlet's WebApplicationContext.*/@Overrideprotected final void initServletBean() throws ServletException {getServletContext().log("Initializing Spring " + getClass().getSimpleName()+ " '" + getServletName() + "'");if (logger.isInfoEnabled()) {logger.info("Initializing Servlet '" + getServletName() + "'");}long startTime = System.currentTimeMillis();try {//创建ApplicationContext容器this.webApplicationContext = initWebApplicationContext();initFrameworkServlet();}catch (ServletException | RuntimeException ex) {logger.error("Context initialization failed", ex);throw ex;}if (logger.isDebugEnabled()) {String value = this.enableLoggingRequestDetails ?"shown which may lead to unsafe logging of potentially sensitive data" :"masked to prevent unsafe logging of potentially sensitive data";logger.debug("enableLoggingRequestDetails='" +this.enableLoggingRequestDetails +"': request parameters and headers will be " + value);}if (logger.isInfoEnabled()) {logger.info("Completed initialization in " + (System.currentTimeMillis()- startTime) + " ms");}}
此处打印的⽇志, 也正是控制台打印出来的⽇志:

 源码跟踪技巧:

在阅读框架源码的时候, ⼀定要抓住关键点, 找到核⼼流程.
切忌从头到尾⼀⾏⼀⾏代码去看, ⼀个⽅法的去研究, ⼀定要找到关键流程, 抓住关键点, 先在宏观上对整个流程或者整个原理有⼀个认识, 有精⼒再去研究其中的细节

初始化web容器的过程中, 会通过onRefresh 来初始化SpringMVC的容器 

  protected WebApplicationContext initWebApplicationContext() {//...if (!this.refreshEventReceived) {//初始化Spring MVCsynchronized (this.onRefreshMonitor) {onRefresh(wac);}}return wac;}@Overrideprotected void onRefresh(ApplicationContext context) {initStrategies(context);}/*** Initialize the strategy objects that this servlet uses.* <p>May be overridden in subclasses in order to initialize further strategy objects.*/protected void initStrategies(ApplicationContext context) {initMultipartResolver(context);initLocaleResolver(context);initThemeResolver(context);initHandlerMappings(context);initHandlerAdapters(context);initHandlerExceptionResolvers(context)initRequestToViewNameTranslator(context);initViewResolvers(context);initFlashMapManager(context);
}
在initStrategies()中进⾏9⼤组件的初始化, 如果没有配置相应的组件,就使⽤默认定义的组件(在
DispatcherServlet.properties中有配置默认的策略, ⼤致了解即可)
⽅法initMultipartResolver、initLocaleResolver、init、initRequestToViewNameTranslator、initFlashMapManager的处理⽅式⼏乎都⼀样(1.2.3.7.8,9),从应⽤⽂中取出指定的Bean, 如果没有, 就使⽤默认的.
⽅法initHandlerMappings、initHandlerAdapters、initHandlerExceptionResolvers的处理⽅式⼏乎
都⼀样(4,5,6)

1.初始化⽂件上传解析器MultipartResolver: 从应⽤上下⽂中获取名称为multipartResolver的Bean,如果没有名为multipartResolver的Bean,则没有提供上传⽂件的解析器
2.初始化区域解析器LocaleResolver: 从应⽤上下⽂中获取名称为localeResolver的Bean,如果没有这个Bean,则默认使⽤AcceptHeaderLocaleResolver作为区域解析器
3.初始化主题解析器ThemeResolver: 从应⽤上下⽂中获取名称为themeResolver的Bean,如果没有这个Bean,则默认使⽤FixedThemeResolver作为主题解析器
4.初始化处理器映射器HandlerMappings: 处理器映射器作⽤,1)通过处理器映射器找到对应的处理器适配器,将请求交给适配器处理;2)缓存每个请求地址URL对应的位置(Controller.xxx⽅法);如果在ApplicationContext发现有HandlerMappings,则从ApplicationContext中获取到所有的HandlerMappings,并进⾏排序;如果在ApplicationContext中没有发现有处理器映射器,则默认BeanNameUrlHandlerMapping作为处理器映射器
5.初始化处理器适配器HandlerAdapter: 作⽤是通过调⽤具体的⽅法来处理具体的请求;如果在 ApplicationContext发现有handlerAdapter,则从ApplicationContext中获取到所有的HandlerAdapter,并进⾏排序;如果在ApplicationContext中没有发现处理器适配器,则默认 SimpleControllerHandlerAdapter作为处理器适配器
6.初始化异常处理器解析器HandlerExceptionResolver: 如果在ApplicationContext发现有
handlerExceptionResolver,则从ApplicationContext中获取到所有的HandlerExceptionResolver,并进⾏排序;如果在ApplicationContext中没有发现异常处理器解析器,则不设置异常处理器

7.初始化RequestToViewNameTranslator: 其作⽤是从Request中获取viewName,从ApplicationContext发现有viewNameTranslator的Bean,如果没有,则默认使⽤DefaultRequestToViewNameTranslator
8.初始化视图解析器ViewResolvers: 先从ApplicationContext中获取名为viewResolver的Bean如果没有,则默认InternalResourceViewResolver作为视图解析器
9.初始化FlashMapManager: 其作⽤是⽤于检索和保存FlashMap(保存从⼀个URL重定向到另⼀个URL时的参数信息),从ApplicationContext发现有flashMapManager的Bean,如果没有,则默认使⽤DefaultFlashMapManager

 1.2 处理请求(核心)

DispatcherServlet 接收到请求后, 执⾏doDispatch 调度⽅法, 再将请求转给Controller.
我们来看doDispatch ⽅法的具体实现:
protected void doDispatch(HttpServletRequest request, HttpServletResponseresponse) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {try {ModelAndView mv = null;Exception dispatchException = null;try {processedRequest = this.checkMultipart(request);multipartRequestParsed = processedRequest != request;//1. 获取执⾏链//遍历所有的 HandlerMapping 找到与请求对应的HandlermappedHandler = this.getHandler(processedRequest);if (mappedHandler == null) {this.noHandlerFound(processedRequest, response);return;}//2. 获取适配器//遍历所有的 HandlerAdapter,找到可以处理该 Handler 的HandlerAdapterHandlerAdapter ha =this.getHandlerAdapter(mappedHandler.getHandler());String method = request.getMethod();boolean isGet = HttpMethod.GET.matches(method);if (isGet || HttpMethod.HEAD.matches(method)) {long lastModified = ha.getLastModified(request,mappedHandler.getHandler());if ((new ServletWebRequest(request,response)).checkNotModified(lastModified) && isGet) {return;}}//3. 执⾏拦截器preHandle⽅法if (!mappedHandler.applyPreHandle(processedRequest, response)){return;}//4. 执⾏⽬标⽅法mv = ha.handle(processedRequest, response,mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}this.applyDefaultViewName(processedRequest, mv);//5. 执⾏拦截器postHandle⽅法mappedHandler.applyPostHandle(processedRequest, response, mv);} catch (Exception var20) {dispatchException = var20;} catch (Throwable var21) {dispatchException = new NestedServletException("Handlerdispatch failed", var21);}//6. 处理视图, 处理之后执⾏拦截器afterCompletion⽅法this.processDispatchResult(processedRequest, response,mappedHandler, mv, (Exception)dispatchException);} catch (Exception var22) {//7. 执⾏拦截器afterCompletion⽅法this.triggerAfterCompletion(processedRequest, response,mappedHandler, var22);} catch (Throwable var23) {this.triggerAfterCompletion(processedRequest, response,mappedHandler, new NestedServletException("Handler processing failed", var23));}} finally {if (asyncManager.isConcurrentHandlingStarted()) {if (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}} else if (multipartRequestParsed) {this.cleanupMultipart(processedRequest);}}}
HandlerAdapter 在 Spring MVC 中使⽤了适配器模式:
适配器模式, 也叫包装器模式. 简单来说就是⽬标类不能直接使⽤, 通过⼀个新类进⾏包装⼀下, 适配调⽤⽅使⽤.
把两个不兼容的接⼝通过⼀定的⽅式使之兼容。
HandlerAdapter 主要⽤于⽀持不同类型的处理器(如 Controller、HttpRequestHandler 或者
Servlet 等),让它们能够适配统⼀的请求处理流程。这样,Spring MVC 可以通过⼀个统⼀的接⼝来处理来⾃各种处理器的请求。
从上述源码可以看出在开始执⾏ Controller 之前,会先调⽤ 预处理⽅法 applyPreHandle,⽽
applyPreHandle ⽅法的实现源码如下:
 boolean applyPreHandle(HttpServletRequest request, HttpServletResponseresponse) throws Exception {for(int i = 0; i < this.interceptorList.size(); this.interceptorIndex =i++) {// 获取项⽬中使⽤的拦截器 HandlerInterceptorHandlerInterceptor interceptor =(HandlerInterceptor)this.interceptorList.get(i);if (!interceptor.preHandle(request, response, this.handler)) {this.triggerAfterCompletion(request, response, (Exception)null);return false;}}return true;}
在 applyPreHandle 中会获取所有的拦截器 HandlerInterceptor , 并执⾏拦截器中的
preHandle ⽅法,这样就会咱们前⾯定义的拦截器对应上了,如下图所⽰:
如果拦截器返回true, 整个发放就返回true, 继续执⾏后。
如果拦截器返回fasle, 则中断后续操作

 

相关文章:

DispatcherServlet 源码分析

一.DispatcherServlet 源码分析 本文仅了解源码内容即可。 1.观察我们的服务启动⽇志: 当Tomcat启动之后, 有⼀个核⼼的类DispatcherServlet, 它来控制程序的执⾏顺序.所有请求都会先进到DispatcherServlet&#xff0c;执⾏doDispatch 调度⽅法. 如果有拦截器, 会先执⾏拦截器…...

代码随想录算法训练营第十八天| 530.二叉搜索树的最小绝对差 ● 501.二叉搜索树中的众数 ● 236. 二叉树的最近公共祖先

题目&#xff1a; 530. 二叉搜索树的最小绝对差 给你一个二叉搜索树的根节点 root &#xff0c;返回 树中任意两不同节点值之间的最小差值 。 差值是一个正数&#xff0c;其数值等于两值之差的绝对值。 示例 1&#xff1a; 输入&#xff1a;root [4,2,6,1,3] 输出&#xff1a;…...

会议室占用的时间(75%用例)D卷(JavaPythonC++Node.jsC语言)

现有若干个会议,所有会议共享--个会议室,用数组表示各个会议的开始时间和结束时间,格式为: 会议1开始时间,会议1结束时间 会议2开始时间,会议2结束时间 请计算会议室占用时间段。 输入描述: 第一行输入一个整数 n,表示会议数量 之后输入n行,每行两个整数,以空格分隔,…...

C++初阶_1:namespace

本章详细解说&#xff1a;namespace 。 namespace&#xff1a; namespace,意为&#xff1a;命名空间&#xff0c;c的关键字&#xff08;关键字&#xff0c;就是提示&#xff1a;取变量名&#xff0c;函数名时不能与之撞名&#xff09;。 namespace的价值&#xff1a; 为了解…...

低代码开发平台:效率革命还是质量隐忧?

如何看待“低代码”开发平台的兴起&#xff1f; 近年来&#xff0c;“低代码”开发平台如雨后春笋般涌现&#xff0c;承诺让非专业人士也能快速构建应用程序。这种新兴技术正在挑战传统软件开发模式&#xff0c;引发了IT行业的广泛讨论。低代码平台是提高效率的利器&#xff0…...

在 Django 表单中传递自定义表单值到视图

在Django中&#xff0c;我们可以通过表单的初始化参数initial来传递自定义的初始值给表单字段。如果我们想要在视图中设置表单的初始值&#xff0c;可以在视图中创建表单的实例时&#xff0c;传递一个字典给initial参数。 1、问题背景 我们遇到了这样一个问题&#xff1a;在使…...

Android之复制文本(TextView)剪贴板

效果图&#xff1a; 功能简单就是点击“复制”&#xff0c;将邀请码复制到 剪贴板中 布局 <androidx.constraintlayout.widget.ConstraintLayoutandroid:id"id/clCode"android:layout_width"dimen/dp_0"android:layout_height"dimen/dp_49"…...

Ubuntu24.04设置国内镜像软件源

参考文章&#xff1a; Ubuntu24.04更换源地址&#xff08;新版源更换方式&#xff09; - 陌路寒暄 一、禁用原来的软件源 Ubuntu24.04 的源地址配置文件发生改变&#xff0c;不再使用以前的 sources.list 文件&#xff0c;升级 24.04 之后&#xff0c;该文件内容变成了一行注…...

分布式与微服务详解

1. 单机架构 只有一台机器&#xff0c;这个机器负责所有的工作 &#xff08;这里假定一个电商网站&#xff09; 现在大部分公司的产品都是单机架构 。 2. 分布式架构 一台机器的硬件资源是有限的&#xff0c;服务器处理请求是需要占用硬件资源的&#xff0c;如果业务增长&a…...

Vue设置滚动条自动保持到最底端

需求描述&#xff1a;在开发中我们常常会遇到需要让滚动条保持到最底端的需求&#xff0c;比如在开发一个聊天框时&#xff0c;请求接口拿到消息列表数据&#xff0c;展示到前端页面时&#xff0c;需要让滚动条自动滚到最底端&#xff0c;以此来展示最后的聊天记录。同时&#…...

uniapp创建一个新项目并导入uview-plus框架

近年来&#xff0c;随着技术的发展&#xff0c;人们越来越意识到跨平台和统一的重要性。对于同一款应用来说&#xff0c;一般都会有移动端、PC端、甚至小程序端。这是由于设备的不同&#xff0c;我们必须要做很多的客户端来满足不同的用户需求。但是由于硬件设施的不同&#xf…...

LabVIEW光电在线测振系统

开发了一种基于LabVIEW软件和光电技术的在线测振系统。该系统利用激光作为调制光源&#xff0c;并通过位置敏感型光电传感器&#xff08;PSD&#xff09;进行轴振动的实时检测。其主要特点包括非接触式测量、广泛的测量范围、高灵敏度和快速响应时间&#xff0c;且具备优良的抗…...

分布式光伏电站 转化能源 丰富用电结构

分布式光伏系统是一种利用分散式的可再生能源&#xff0c;在靠近用户端的地方安装光伏发电设施&#xff0c;通过光伏效应将太阳能转化为直流电能&#xff0c;并通过逆变器将其转换为交流电&#xff0c;以供用户使用的系统。以下是对分布式光伏系统的详细阐述&#xff1a; 一、…...

环境配置:如何在IntelliJ IDEA中安装和修改JDK版本配置(以Windows为例)

环境配置&#xff1a;如何在IntelliJ IDEA中安装和修改JDK版本配置&#xff08;以Windows为例&#xff09; 为了在Java开发中使用最新的功能和优化&#xff0c;升级和配置JDK版本是必不可少的。本文将详细介绍如何下载、安装、配置最新的JDK版本&#xff0c;并在IntelliJ IDEA…...

Spring AOP 原理——代理模式

目录 一、代理模式 1.1 静态代理 1.2 动态代理 1.2.1 JDK动态代理 1.2.2 CGLIB动态代理 Spring AOP 是基于动态代理来实现AOP的。 一、代理模式 代理模式, 也叫委托模式。该模式是为其他对象提供⼀种代理以控制对这个对象的访问。它的作用就是通过提供一个代理类&#…...

leetcode 234.回文链表

思路&#xff1a;其实就是判断反转链表是不是和原链表一样的问题。 我们可以借助反转链表的思路&#xff0c;首先我们先把链表的全部元素正向存储&#xff0c;然后再把链表进行反转。 之后我们再遍历反转之后的链表结点元素是不是和刚刚存储数组里面的元素一致就可以了。一旦…...

AD中Split Planes 的作用和功能

在 Altium Designer (AD) 中&#xff0c;Split Planes 功能允许你在一个平面层&#xff08;例如电源层或地层&#xff09;上分割出多个不同的区域&#xff0c;每个区域可以分配给不同的网络&#xff08;net&#xff09;。这对于设计中需要管理多种电源或接地类型的情况下非常有…...

[linux][命令]linux文件操作命令大全

Linux操作系统提供了丰富的文件操作命令&#xff0c;以下是一些常用的文件操作命令列表&#xff1a; 查看文件内容 cat&#xff1a;查看文件内容。less&#xff1a;分页显示文件内容。more&#xff1a;分页显示文件内容&#xff0c;一次显示一屏。head&#xff1a;查看文件的前…...

大语言模型 (LLM) 窥探未来

初始的探索 在NLP领域&#xff0c;早期的模型如 LSTM 和 GRU 在处理序列数据时取得了一定的成功。但随着数据量和复杂性的增加&#xff0c;这些模型开始显得力不从心。 Transformer的诞生 Transformer 模型的提出&#xff0c;它通过自注意力&#xff08;Self-Attention&…...

WPF DataGrid调试错误总结

最近WPF中使用了DataGrid做了表格&#xff0c;框架版本为472&#xff0c;遇到了不少的问题&#xff0c;因为软件添加了一个退出进程的全局错误捕获&#xff0c;因此不得不解决所有问题&#xff0c;这边总结一下DataGrid的问题 EditItem is not allowed for this view 按字面意…...

React 第五十五节 Router 中 useAsyncError的使用详解

前言 useAsyncError 是 React Router v6.4 引入的一个钩子&#xff0c;用于处理异步操作&#xff08;如数据加载&#xff09;中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误&#xff1a;捕获在 loader 或 action 中发生的异步错误替…...

51c自动驾驶~合集58

我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留&#xff0c;CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制&#xff08;CCA-Attention&#xff09;&#xff0c;…...

MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例

一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...

循环冗余码校验CRC码 算法步骤+详细实例计算

通信过程&#xff1a;&#xff08;白话解释&#xff09; 我们将原始待发送的消息称为 M M M&#xff0c;依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)&#xff08;意思就是 G &#xff08; x ) G&#xff08;x) G&#xff08;x) 是已知的&#xff09;&#xff0…...

JDK 17 新特性

#JDK 17 新特性 /**************** 文本块 *****************/ python/scala中早就支持&#xff0c;不稀奇 String json “”" { “name”: “Java”, “version”: 17 } “”"; /**************** Switch 语句 -> 表达式 *****************/ 挺好的&#xff…...

IT供电系统绝缘监测及故障定位解决方案

随着新能源的快速发展&#xff0c;光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域&#xff0c;IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选&#xff0c;但在长期运行中&#xff0c;例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...

【Java学习笔记】BigInteger 和 BigDecimal 类

BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点&#xff1a;传参类型必须是类对象 一、BigInteger 1. 作用&#xff1a;适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...

Java求职者面试指南:计算机基础与源码原理深度解析

Java求职者面试指南&#xff1a;计算机基础与源码原理深度解析 第一轮提问&#xff1a;基础概念问题 1. 请解释什么是进程和线程的区别&#xff1f; 面试官&#xff1a;进程是程序的一次执行过程&#xff0c;是系统进行资源分配和调度的基本单位&#xff1b;而线程是进程中的…...

【Nginx】使用 Nginx+Lua 实现基于 IP 的访问频率限制

使用 NginxLua 实现基于 IP 的访问频率限制 在高并发场景下&#xff0c;限制某个 IP 的访问频率是非常重要的&#xff0c;可以有效防止恶意攻击或错误配置导致的服务宕机。以下是一个详细的实现方案&#xff0c;使用 Nginx 和 Lua 脚本结合 Redis 来实现基于 IP 的访问频率限制…...

适应性Java用于现代 API:REST、GraphQL 和事件驱动

在快速发展的软件开发领域&#xff0c;REST、GraphQL 和事件驱动架构等新的 API 标准对于构建可扩展、高效的系统至关重要。Java 在现代 API 方面以其在企业应用中的稳定性而闻名&#xff0c;不断适应这些现代范式的需求。随着不断发展的生态系统&#xff0c;Java 在现代 API 方…...