Spring5源码14-SpringMVC-DispatcherServlet处理请求核心逻辑

Spring进阶 - SpringMVC实现原理之DispatcherServlet处理请求的过程 前文我们有了IOC的源码基础以及SpringMVC的基础,我们便可以进一步深入理解SpringMVC主要实现原理,包含DispatcherServlet的初始化过程和DispatcherServlet处理请求的过程的源码解析。本文是第二篇DispatcherServlet处理请求的过程的源码解析。@pdai。... 阅读详情

1. Spring mvc 流程

其时序图:

整体流程图:

SpringMVC 工作流程描述:

  1. 用户向服务器发送请求,请求被Spring 前端控制Servelt DispatcherServlet 捕获;
  2. DispatcherServlet 对请求URL进行解析,得到请求资源标识符(URI)。然后根据该URI,调用 HandlerMapping 获得该 Handler 配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以 HandlerExecutionChain 对象的形式返回;
  3. DispatcherServlet 根据获得的 Handler ,选择一个合适的 HandlerAdapter 。(附注:如果成功获得 HandlerAdapter 后,此时将开始执行拦截器的preHandler(…)方法)
  4. 提取 Request 中的模型数据,填充 Handler 入参,开始执行Handler(Controller)。 在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:
    • HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息
    • 数据转换:对请求消息进行数据转换。如String转换成Integer、Double等
    • 数据根式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等
    • 数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中
  5. Handler执行完成后,向 DispatcherServlet 返回一个 ModelAndView 对象;
  6. 根据返回的 ModelAndView ,选择一个适合的 ViewResolver (必须是已经注册到Spring容器中的 ViewResolver )返回给 DispatcherServlet ;
  7. ViewResolver 结合 Model 和 View ,来渲染视图
  8. 将渲染结果返回给客户端。

2. HttpServlet & FrameworkServlet

下面我们先看看 DispatcherServlet 的两个父类: HttpServlet 和 FrameworkServlet 。

FrameworkServlet 重写了 HttpServlet 的 service 方法。 我们这里先来看看 FrameworkServlet#service 方法。

@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
        // 解析请求方式
        HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
        if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
                processRequest(request, response);
        }
        else {
                super.service(request, response);
        }
}
复制代码

这里我们需要关注两个点 : processRequest(request, response); 和 super.service(request, response); 。下面我们一一来看

2.1 HttpServlet#service

我们知道 HttpServlet 类中分别提供了相应的服务方法( doGet、doPost 等),如下图

同时, HttpServlet 会根据请求的不同形式引导到对应导函数中处理,如下 HttpServlet#service(HttpServletRequest, HttpServletResponse) :

protected void service(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException {

    String method = req.getMethod();
            // 如果是 get 方法
    if (method.equals(METHOD_GET)) {
            //  lastModified  缓存判断
        long lastModified = getLastModified(req);
        if (lastModified == -1) {
            // servlet doesn't support if-modified-since, no reason
            // to go through further expensive logic
            doGet(req, resp);
        } else {
            // 如果设置了缓存时间,则判断在ifModifiedSince  之后的时间,是否被修改。
            long ifModifiedSince;
            try {
                ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
            } catch (IllegalArgumentException iae) {
                // Invalid date header - proceed as if none was set
                ifModifiedSince = -1;
            }
            if (ifModifiedSince < (lastModified / 1000 * 1000)) {
                // If the servlet mod time is later, call doGet()
                // Round down to the nearest second for a proper compare
                // A ifModifiedSince of -1 will always be less
                maybeSetLastModified(resp, lastModified);
                doGet(req, resp);
            } else {
                resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
            }
        }

    }
    // 分发不同的请求
    else if (method.equals(METHOD_HEAD)) {
        long lastModified = getLastModified(req);
        maybeSetLastModified(resp, lastModified);
        doHead(req, resp);

    } else if (method.equals(METHOD_POST)) {
        doPost(req, resp);

    } else if (method.equals(METHOD_PUT)) {
        doPut(req, resp);

    } else if (method.equals(METHOD_DELETE)) {
        doDelete(req, resp);

    } else if (method.equals(METHOD_OPTIONS)) {
        doOptions(req,resp);

    } else if (method.equals(METHOD_TRACE)) {
        doTrace(req,resp);

    } else {
        //
        // Note that this means NO servlet supports whatever
        // method was requested, anywhere on this server.
        String errMsg = lStrings.getString("http.method_not_implemented");
        Object[] errArgs = new Object[1];
        errArgs[0] = method;
        errMsg = MessageFormat.format(errMsg, errArgs);

        resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
    }
}
复制代码

而这几个函数最常用的就是 doGet() 和 doPost() 。这两个方法被 FrameworkServlet 重写了。我们来看看在 FrameworkServlet 中的实现。

@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {

        processRequest(request, response);
}

@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {

        processRequest(request, response);
}
... 
复制代码

我们可以很清楚的看到,对于大部分的请求,还是依赖于 HttpServlet#service(HttpServletRequest, HttpServletResponse) 来进行一个请求的分发。对于我们常见的 doGet() 和 doPost() 方法都是直接调用 processRequest(request, response); , 而 processRequest 方法 的具体实现在 FrameworkServlet#processRequest 中 。

2.2 FrameworkServlet#processRequest

因此。接下来我们就来看看 org.springframework.web.servlet.FrameworkServlet#processRequest :

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {

   // 记录当前时间,用于记录web请求的记录时间
   long startTime = System.currentTimeMillis();
   Throwable failureCause = null;

   // 1234 的目的是为了保证当前线程的 LocaleContext 和 RequestAttributes 在当前请求后还能恢复,所以提取保存
   // 1. 提取当前线程的 LocaleContext  属性
   LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
   // 2. 根据当前的request 创建对应的 LocaleContext ,并绑定到当前线程
   LocaleContext localeContext = buildLocaleContext(request);

   // 3. 提取当前线程的 RequestAttributes 属性
   RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
   // 4. 根据当前的request 创建对应的 RequestAttributes ,并绑定到当前线程
   ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

   WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
   asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

   initContextHolders(request, localeContext, requestAttributes);

   try {
      // todo 5. 委托给 doservice方法进行进一步处理
      doService(request, response);
   }
   catch (ServletException | IOException ex) {
      failureCause = ex;
      throw ex;
   }
   catch (Throwable ex) {
      failureCause = ex;
      throw new NestedServletException("Request processing failed", ex);
   }

   finally {
      // 6. 请求结束,恢复线程原状
      resetContextHolders(request, previousLocaleContext, previousAttributes);
      if (requestAttributes != null) {
         requestAttributes.requestCompleted();
      }
      logResult(request, response, failureCause, asyncManager);
      // 发布请求结束的通知事件,无论成功与否
      publishRequestHandledEvent(request, response, startTime, failureCause);
   }
}
复制代码

由于逻辑都被封装到 doService(request, response); 中,所以这里还是比较简单。而doService又被 DispatcherServlet 实现了。因此我们这里来看看 DispatcherServlet#doService 。

  1. DispatcherServlet#doService
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
   logRequest(request);

   // Keep a snapshot of the request attributes in case of an include,
   // to be able to restore the original attributes after the include.
   Map<String, Object> attributesSnapshot = null;
   if (WebUtils.isIncludeRequest(request)) {
      attributesSnapshot = new HashMap<>();
      Enumeration<?> attrNames = request.getAttributeNames();
      while (attrNames.hasMoreElements()) {
         String attrName = (String) attrNames.nextElement();
         if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
            attributesSnapshot.put(attrName, request.getAttribute(attrName));
         }
      }
   }

   // Make framework objects available to handlers and vi
Spring MVC过滤器与拦截器:从原理到实战的完整解析 在Java Web开发中,请求处理链是构建健壮应用架构的核心机制。其基本原理是通过在HTTP请求与响应的生命周期中插入可插拔的组件,实现对流量的统一管控与业务逻辑的织入。这一技术价值在于解耦横切关注点,提升代码复用性与可维护性。典型的应用场景包括全局安全防护、日志记录、权限校验与数据预处理等。本文聚焦于Spring MVC框架中两个关键组件——过滤器和拦截器,深入剖析其核心差异。过滤器作为Servlet规范定义的‘门户保安’,作用于更底层的容器级别,擅长处理全局性、基础性任务;而拦截器作为Spring框架的 阅读详情

相关推荐

SSM框架整合实战:Spring+SpringMVC+MyBatis企业级开发指南

SSM框架整合是Java企业级开发的核心技术栈,由Spring、SpringMVC和MyBatis三大框架组成。Spring框架通过IoC容器实现组件管理,AOP支持面向切面编程;SpringMVC基于MVC模式处理Web请求;MyBatis作为ORM框架简化数据库操作。这种组合相比传统SSH架构更轻量高效,特别适合构建高并发Web应用。在电商系统、金融平台等实际项目中,SSM框架通过分层架构实现业务解耦,配合Maven进行依赖管理,使用Druid连接池优化数据库访问。本文详细演示了从环境搭建、配置整合到C

weixin_30268071的博客 451

【SpringBoot 4.x 第61节】Spring MVC 请求处理链路源码解析:DispatcherServlet、HandlerMapping 与 HandlerAdapter!

🏆 本文收录于 《滚雪球学 Spring Boot 4.x》 专栏。本专栏面向 有一定 Java 基础,但尚未系统学习 Spring Boot 的读者,采用“滚雪球式学习法”:先跑通、再理解、再重构、再上线,带你从第一个 Hello API 开始,逐步完成一个可部署、可监控、可扩展的后端项目。

**My Coding Family** 327

微信小程序PC端兼容性实战:如何绕过限制在电脑上运行手机端小程序

本文详细解析了微信小程序在PC端的兼容性问题及解决方案,重点介绍了如何利用`wx.getSystemInfo`API检测运行环境,并通过修改User-Agent、动态环境检测与适配等技术手段实现手机端小程序在PC端的顺畅运行。文章还提供了常见兼容性问题的解决方法和调试工具推荐,帮助开发者高效应对多端适配挑战。

weixin_28366053的博客 867

新疆地形数据下载:30米、12.5米、5米DEM地形数据

新疆是我国陆地面积最大的省级行政区,总面积达166万平方公里。新疆的地形也十分的复杂,新疆的地形呈山脉与盆地相间排列,盆地被高山环抱,,俗称“三山夹两盆”。三山指阿尔泰山、昆仑山、天山,两盆是指塔里木盆地和准噶尔盆地。 对精度要求不高的话,可以直接使用图新地球下载 新疆30米地形数据 大小:9.26GB 来自ASTER v3的新疆30米地形数据 30米地形数据下载 新疆12.5米地形数据 12.5米DEM数据由于精度比较高,一般不会用到全省的数据,通常用的是一个地区的,因此,可以单独分

LocaSpace的博客 3495

手把手教你学Simulink——基于重复控制(RC)的并网逆变器场景实例:基于RC的单相并网逆变器仿真

本次教程深入剖析了重复控制(RC)在单相并网逆变器中的应用,解决了周期性谐波抑制这一核心难题。核心收获RC是谐波抑制的“核武器”:它利用内模原理,对所有周期性谐波提供无限大增益,实现理论上无静差的跟踪和抑制。PI+RC是黄金组合:PI提供快速响应,RC提供稳态精度,二者结合,兼顾了动态与稳态性能。结构简单,效果卓越:相比多个PR控制器,RC仅需一个模块,即可抑制所有谐波,设计简洁。电能质量的终极追求,是波形的纯净。重复控制,如同一位不知疲倦的校对员它记住每一次错误(上一周期的误差),

MHD0815的博客 189

小麦病害检测系统开发

本项目旨在针对小麦病害小目标检测任务,在YOLOv11基础上进行三项改进: - 将主干网络替换为Swin Transformer,增强全局特征提取能力。 - 在检测头中引入DynamicHead,并增加检测头数量至四个,提升多尺度检测性能。 - 集成半监督FixMatch训练框架,利用无标签数据提升模型泛化能力。 我们定义五个模型进行对比实验: - Model 0: 原始YOLOv11 (有监督) - Model 1: YOLOv11 + Swin Transformer (有监督) ~~~~~~

AI的内部世界的博客 121

使用 Visual Studio Code 连接 ABAP On-Premise 系统做开发的详细配置步骤

本文详细介绍了如何在Visual Studio Code中通过ABAP Development Tools扩展连接ABAP On-Premise系统。关键步骤包括:通过Ctrl+Shift+P打开命令行面板选择「ABAP: New Destination...」创建RFC连接(仅支持SSO单点登录),选择本地SAPGUI中已有的系统,输入Client ID、语言和用户名(无需密码),最后将目标系统添加到工作区即可开始开发。文章强调了企业安全考虑下仅支持SSO的设计理念,并展示了连接配置的JSON格式示例。整

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 987

大语言模型应用指南:幻觉和偏见问题

大语言模型应用指南:幻觉和偏见问题 1. 背景介绍 1.1 大语言模型的发展历程 1.1.1 早期的语言模型 1.1.2 Tr

AI天才研究院 958

【入坑存内计算一键指南】存内计算运算原理:算法优化、存储介质探究

本文探讨了存内计算及其面向的架构优化,尤其侧重于图算法优化和存内计算存储介质。文章介绍了存内计算的概念和意义,着重强调了存内计算解决了内存墙问题,提高了计算效率。针对图算法的优化,提出了基于位运算的创新方法,重塑了单源最短路径算法,并介绍了基于局部重叠度的链路预测算法优化设计。接着,文章详细讨论了存内计算存储介质,特别是非易失性存储器,比较了不同类型存储器的性能,并重点介绍了基于ReRAM的存内计算技术,包括其物理模型和交叉阵列结构。

一键难忘的博客 7067

手把手教你学PCIE--所有 PF(Physical Function)共同管理和分配 VF

但具体如何分配和使用 VF 取决于硬件设计、驱动程序配置以及实际应用场景。

MHD0815的博客 1462

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

上一篇: 声明式HTTP客户端-Feign 使用入门详解
下一篇: Dubbo源码- 服务调用过程
java小姜在线冲
博客等级 码龄4年 53粉丝 137原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值