首页 > 编程语言 >DispatcherServlet 请求处理源码分析

DispatcherServlet 请求处理源码分析

时间:2025-01-03 15:11:21浏览次数:1  
标签:null return 请求 mappedHandler request handler DispatcherServlet 源码 processedReque

业务处理流程

  1. 请求匹配:通过 HandlerMapping 查找合适的处理器。
  2. 拦截器前置处理:执行所有的 HandlerInterceptorpreHandle 方法。
  3. 执行控制器方法:调用相应的控制器方法处理请求。
  4. 数据处理
    • 如果是视图返回,进行视图解析并渲染。
    • 如果是数据返回,使用 HttpMessageConverter 转换成合适的格式(如 JSON)。
  5. 拦截器后处理:执行所有 HandlerInterceptorpostHandle 方法。
  6. 视图渲染:将模型数据和视图渲染成最终的响应内容。
  7. 后置清理:执行所有 HandlerInterceptorafterCompletion 方法。
  8. 异常处理:如果有异常发生,进行异常处理。

doDispatch 源码

@SuppressWarnings("deprecation")
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
  
  // 方法内部变量
  HttpServletRequest processedRequest = request;
  HandlerExecutionChain mappedHandler = null;
  boolean multipartRequestParsed = false;
  
	// 异步请求处理(这里是先拿到当前请求的异步管理器,异步请求还不熟悉~)
  WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

  try {
    ModelAndView mv = null;
    Exception dispatchException = null;

    // 请求处理(这里后面详细分析,1、2、3)
    try { ... }
    catch (Exception ex) {
      // 如果发生异常,赋值给 dispatchException 变量
      dispatchException = ex;
    }
    catch (Throwable err) {
      // 如果发生错误,也赋值给 dispatchException
      dispatchException = new ServletException("Handler dispatch failed: " + err, err);
    }
    // 结果处理(后面详细分析,4、5、6)
    // 1,如果 dispatchException 不为空,说明请求处理发生了异常,使用异常解析器处理异常(如果异常解析器也发生异常呢?所以外层还有一层 try catch)
    // 2,如果 dispatchException 为空,说明请求处理正常,该返回视图或 json 都是这里做的
    // 3,也会执行拦截器的 afterCompletion 方法
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
  }
  catch (Exception ex) {
    // 如果异常解析器也发生异常,也要执行拦截器的 afterCompletion 方法
    triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
  }
  catch (Throwable err) {
    // 如果异常解析器也发生错误,也要执行拦截器的 afterCompletion 方法
    triggerAfterCompletion(processedRequest, response, mappedHandler,
        new ServletException("Handler processing failed: " + err, err));
  }
  finally {
    // 如果是异步请求的后续处理
    if (asyncManager.isConcurrentHandlingStarted()) {
      if (mappedHandler != null) {
        mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
      }
    }
    else {
      // 如果是文件请求的后续处理
      if (multipartRequestParsed) {
        cleanupMultipart(processedRequest);
      }
    }
  }
}

通过上面源码分析可以知道

  1. 不管 handler 执行异常还是成功,拦截器的 afterCompletion 一定会执行(顺序是倒序执行)
  2. 内层 try catch 是处理 handler 执行异常,外层 try catch 是当异常解析器处理也发生异常的处理

寻找和执行 handler 源码

也就是内层的 try catch 不分,源码如下

// 1,检查是否是文件上传请求
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);

// 2,寻找处理器 Handler
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
  noHandlerFound(processedRequest, response);
  return;
}

// 3,寻找 HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

// get head 请求用于获取资源,静态资源比如 css、图片等,动态资源比如 jsp、freemarket、servlet 等
// 当资源没有被修改并且缓存未失效就返回缓存
// 前后分离后这个缓存就没啥意义了
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;
  }
}

// 执行拦截器,如果不放行,请求处理结束,不会执行 handler
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
  return;
}

// 真正执行 handler
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

if (asyncManager.isConcurrentHandlingStarted()) {
  return;
}

applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);

检查是否是文件上传请求

// org.springframework.web.servlet.DispatcherServlet#checkMultipart
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
    // 文件解析器不为空并且 ContentType 为 multipart/form-data 打头
    if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
        // request 是否是 MultipartHttpServletRequest(如果不是会返回 null)
        if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
            if (DispatcherType.REQUEST.equals(request.getDispatcherType())) {
                logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter");
            }
        } else if (hasMultipartException(request)) {
            logger.debug("Multipart resolution previously failed for current request - " +
                    "skipping re-resolution for undisturbed error rendering");
        } else {
            try {
                // 使用文件解析器解析请求
                return this.multipartResolver.resolveMultipart(request);
            } catch (MultipartException ex) {
                if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
                    logger.debug("Multipart resolution failed for error dispatch", ex);
                    // Keep processing error dispatch with regular request handle below
                } else {
                    throw ex;
                }
            }
        }
    }
    // 文件解析器肯定不会空,内置好了的。如果 ContentType 为=不是以 multipart/form-data 打头,就返回原 request
    return request;
}

// org.springframework.web.multipart.support.StandardServletMultipartResolver#resolveMultipart
public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {
    // 成员变量 resolveLazily 默认值是 false
    return new StandardMultipartHttpServletRequest(request, this.resolveLazily);
}

// org.springframework.web.multipart.support.StandardMultipartHttpServletRequest#StandardMultipartHttpServletRequest(jakarta.servlet.http.HttpServletRequest, boolean)
public StandardMultipartHttpServletRequest(HttpServletRequest request, boolean lazyParsing) throws MultipartException {
  	// 调用到好几辈的父类构造,目的就是赋值,把 request 赋值给成员变量
    super(request);
    // StandardMultipartHttpServletRequest 的成员变量 lazyParsing 默认就是 false
    if (!lazyParsing) {
      	// 这里真正解析
        this.parseRequest(request);
    }
}

// org.springframework.web.multipart.support.StandardMultipartHttpServletRequest#parseRequest
private void parseRequest(HttpServletRequest request) {
    try {
        // Servlet 3.0 新引入的 API,当 Content-Type 为 multipart/form-data 时方便从 HTTP 请求中获取表单字段和文件
        Collection<Part> parts = request.getParts();
        this.multipartParameterNames = new LinkedHashSet(parts.size());
        MultiValueMap<String, MultipartFile> files = new LinkedMultiValueMap(parts.size());
				// Part 是代表请求中的每一部分的对象。每个 Part 对象可以包含上传的文件或普通的表单字段
        for (Part part : parts) {
            String headerValue = part.getHeader("Content-Disposition");
            ContentDisposition disposition = ContentDisposition.parse(headerValue);
            String filename = disposition.getFilename();
            if (filename != null) {
                files.add(part.getName(), new StandardMultipartFile(part, filename));
            } else {
                this.multipartParameterNames.add(part.getName());
            }
        }
        // 就不贴源码了,源码就是下面这行注释,这个方法就是把 parts 解析后的 map 赋值给 multipartFiles 变量
        // this.multipartFiles = new LinkedMultiValueMap(Collections.unmodifiableMap(multipartFiles));
        this.setMultipartFiles(files);
    } catch (Throwable ex) {
        this.handleParseFailure(ex);
    }
}

寻找 Handler

// org.springframework.web.servlet.DispatcherServlet#getHandler
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
  // MVC 启动时会维护好 handlerMappings
  // 默认有 RequestMappingHandlerMapping(处理@RequestMapping注解实现的 handler)、RouterFunctionmapping 等
  if (this.handlerMappings != null) {
    // 遍历所有的 处理器映射器 (处理器映射器维护了 url 和 handler,根据 url 就能找到 handler)
    for (HandlerMapping mapping : this.handlerMappings) {
      // 得到的是 处理器执行链(处理器+拦截器)
      HandlerExecutionChain handler = mapping.getHandler(request);
      if (handler != null) {
        return handler;
      }
    }
  }
  return null;
}

// org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
  // 获取 handler(RequestMappingHandlerMapping 的 mappingRegistry 的 registry 中维护了所有 url 和 handler)
  // 这个方法内部还是有点逻辑,打个断点看下看下处理器映射器的各个属性就会比较清晰了
  Object handler = getHandlerInternal(request);
  if (handler == null) {
    handler = getDefaultHandler();
  }
  if (handler == null) {
    return null;
  }
  // 获取到的 handler 是否是字符串,如果是字符串就找对应的 bean 作为 handler
  if (handler instanceof String handlerName) {
    handler = obtainApplicationContext().getBean(handlerName);
  }

  // 请求缓存(把请求url写到request属性中,后续再处理就不用计算url了,直接可以从request中拿到)
  if (!ServletRequestPathUtils.hasCachedPath(request)) {
    initLookupPath(request);
  }

  // 这个方法比较简单,就是取出所有拦截器加上 handler 组合成 HandlerExecutionChain(这里就会用到上面写到request的url)
  HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

  if (logger.isTraceEnabled()) {
    logger.trace("Mapped to " + handler);
  }
  else if (logger.isDebugEnabled() && !DispatcherType.ASYNC.equals(request.getDispatcherType())) {
    logger.debug("Mapped to " + executionChain.getHandler());
  }

  // 跨域处理
  if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
    CorsConfiguration config = getCorsConfiguration(handler, request);
    if (getCorsConfigurationSource() != null) {
      CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request);
      config = (globalConfig != null ? globalConfig.combine(config) : config);
    }
    if (config != null) {
      config.validateAllowCredentials();
    }
    executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
  }
	// 返回处理器执行链
  return executionChain;
}

寻找 HandlerAdapter

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
  // 通处理器映射器一致,处理器适配器也是mvc启动就维护好了,也有好几种类型,RequestMappingHandlerAdapter 处理 @RequestMapping 注解实现的 handler
  if (this.handlerAdapters != null) {
    for (HandlerAdapter adapter : this.handlerAdapters) {
      // 每个适配器通过 supports 决定支持处理哪种 handler
      if (adapter.supports(handler)) {
        return adapter;
      }
    }
  }
  throw new ServletException("No adapter for handler [" + handler +
      "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

// 实现 jakarta.servlet.Servlet 接口的 handler 对应的适配器
public boolean supports(Object handler) {
  return (handler instanceof Servlet);
}

// 实现 org.springframework.web.servlet.mvc.Controller 接口的 handler 对应的适配器
public boolean supports(Object handler) {
  return (handler instanceof Controller);
}

// @RequestMapping 实现的 handler 对应的适配器(会被封装为 HandlerMethod 对象)
public final boolean supports(Object handler) {
  return (handler instanceof HandlerMethod handlerMethod && supportsInternal(handlerMethod));
}

标签:null,return,请求,mappedHandler,request,handler,DispatcherServlet,源码,processedReque
From: https://www.cnblogs.com/cyrushuang/p/18650135

相关文章

  • 【2025最新计算机毕业设计】基于Java的旧衣淘淘网(高质量源码,提供文档,免费部署到本地)【
     作者简介:✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流。✌ 主要内容:......
  • 基于微信小程序的二手物品交易平台ssm+论文源码调试讲解
    第4章系统设计一个成功设计的系统在内容上必定是丰富的,在系统外观或系统功能上必定是对用户友好的。所以为了提升系统的价值,吸引更多的访问者访问系统,以及让来访用户可以花费更多时间停留在系统上,则表明该系统设计得比较专业。4.1设计原则本系统在设计过程中需要依照一定......
  • (免费送源码)计算机毕业设计原创定制:Java+springboot+HTML+CSS spring boot 停车场管理
    摘 要科技进步的飞速发展引起人们日常生活的巨大变化,电子信息技术的飞速发展使得电子信息技术的各个领域的应用水平得到普及和应用。信息时代的到来已成为不可阻挡的时尚潮流,人类发展的历史正进入一个新时代。在现实运用中,应用软件的工作规则和开发步骤,采用Java技术建设停车......
  • springboot541党员学习交流平台(论文+源码)_kaic
     摘 要如今社会上各行各业,都喜欢用自己行业的专属软件工作,互联网发展到这个时候,人们已经发现离不开了互联网。新技术的产生,往往能解决一些老技术的弊端问题。因为传统党员学习交流平台信息管理难度大,容错率低,管理人员处理数据费工费时,所以专门为解决这个难题开发了一个党员......
  • springboot542健身房管理系统(论文+源码)_kaic
     摘 要传统办法管理信息首先需要花费的时间比较多,其次数据出错率比较高,而且对错误的数据进行更改也比较困难,最后,检索数据费事费力。因此,在计算机上安装健身房管理系统软件来发挥其高效地信息处理的作用,可以规范信息管理流程,让管理工作可以系统化和程序化,同时,健身房管理系统......
  • HTML5期末大作业:基于HTML+CSS+JavaScript仿蘑菇街购物商城设计毕业论文源码 (1)
    常见网页设计作业题材有个人、美食、公司、学校、旅游、电商、宠物、电器、茶叶、家居、酒店、舞蹈、动漫、服装、体育、化妆品、物流、环保、书籍、婚纱、游戏、节日、戒烟、电影、摄影、文化、家乡、鲜花、礼品、汽车、其他等网页设计题......
  • 由 Mybatis 源码畅谈软件设计(九):“能用就行” 其实远远不够
    作者:京东保险王奕龙到本节Mybatis源码中核心逻辑基本已经介绍完了,在这里我想借助Mybatis其他部分源码来介绍一些我认为在编程中能最快提高编码质量的小方法,它们可能比较细碎,希望能对大家有所启发。关于方法的长度和方法拆分之前我在读完《代码整洁之道》时,非常痴迷于写小......
  • 【前端开发】前端接口防止重复请求实现方案
    #薅羊毛前言前段时间老板心血来潮,要我们前端组对整个的项目都做一下接口防止重复请求的处理(似乎是有用户通过一些快速点击薅到了一些优惠券啥的)。。。听到这个需求,第一反应就是,防止薅羊毛最保险的方案不还是在服务端加限制吗?前端加限制能够拦截的毕竟有限。可老板就是执意要前端......
  • 同城兴趣社区社交圈子小程序轻型创业分享,交流,打造优质圈子系统源码平台上线即可运营
    目前我们圈子系统有开源版/商业版、陪玩开黑等插件。 系统优势1.多端覆盖,支持APP、小程序和H5三端访问,满足不同用户在不同场景下的使用需求2.开发成本与维护,使用uniapp等跨平台开发框架,可以显著降低开发成本。3.社交互动与用户体验,提供丰富的社交互动功能,如点赞、评论、......
  • C/S客户端程序 winform接收外部http (GET|POST)请求 工具类逻辑开发
    前言我们知道web项目(即B/S端程序的S端)是很容易提供API接口,供外部进行访问的,这是Web本身的特性所然。Web项目在发布后,会挂载到比如IIS管理器,上面会要求配置IP和端口号,外部访问时根据约定的IP,端口,以及约定的路由路径、请求方式、传参等就很容易外部对内API接口访问。客户端程序(......