DispatcherServlet请求处理流程

前言

DispatcherServlet 是 Spring MVC 的核心类,它本质是一个 Servlet,负责接管 HTTP 请求并把它分发给对应的处理器处理,最后处理响应结果渲染页面。
DispatcherServlet 本身并不复杂,它提供了一个模板方法doDispatch()来处理请求,把请求处理的细节交给了依赖的其它组件。
本文先分析 DispatcherServlet 的初始化流程,再分析请求的整体处理流程,至于请求处理中涉及到的其它组件,会另起篇幅。

初始化

首先看一下 DispatcherServlet 的类图:
image.png
DispatcherServlet 本质是一个 Servlet,所以它实现了 Servlet 接口,也就有对应的 Servlet 生命周期方法,在 Servlet 容器启动时,会触发其init()生命周期方法。
image.png
HttpServletBean#init会基于 ServletConfig 配置初始化 bean,Spring 会使用 BeanWrapper 操作 bean 属性,最后调用initServletBean()初始化子类。

public final void init() throws ServletException {PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);if (!pvs.isEmpty()) {try {BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));initBeanWrapper(bw);bw.setPropertyValues(pvs, true);}catch (BeansException ex) {if (logger.isErrorEnabled()) {logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);}throw ex;}}initServletBean();
}

FrameworkServlet#initServletBean的核心是初始化 WebApplicationContext,然后预留了一个钩子函数initFrameworkServlet()给子类扩展。

protected final void initServletBean() throws ServletException {this.webApplicationContext = initWebApplicationContext();initFrameworkServlet();
}

initWebApplicationContext()用于初始化 Web 容器上下文对象,Spring 首先会在 ServletContext 查找根容器,然后判断 DispatcherServlet 实例化时是否设置上下文对象,如果没有设置则基于ContextClass反射创建上下文对象。

protected WebApplicationContext initWebApplicationContext() {WebApplicationContext rootContext =WebApplicationContextUtils.getWebApplicationContext(getServletContext());WebApplicationContext wac = null;if (this.webApplicationContext != null) {wac = this.webApplicationContext;if (wac instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;if (!cwac.isActive()) {if (cwac.getParent() == null) {cwac.setParent(rootContext);}configureAndRefreshWebApplicationContext(cwac);}}}if (wac == null) {wac = findWebApplicationContext();}if (wac == null) {wac = createWebApplicationContext(rootContext);}if (!this.refreshEventReceived) {synchronized (this.onRefreshMonitor) {onRefresh(wac);}}if (this.publishContext) {String attrName = getServletContextAttributeName();getServletContext().setAttribute(attrName, wac);}return wac;
}

上下文对象初始化完成后,最后会触发onRefresh()模板方法完成子类的初始化。对于 DispatcherServlet,主要是初始化一些依赖的组件类,比如:文件上传解析器、处理器映射器、异常处理器、视图解析器等等。

protected void onRefresh(ApplicationContext context) {initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {// 多文件上传解析器initMultipartResolver(context);// 国际化支持initLocaleResolver(context);// 主题解析器initThemeResolver(context);// 处理器映射器initHandlerMappings(context);// 处理器适配器initHandlerAdapters(context);// 异常处理解析器initHandlerExceptionResolvers(context);// 请求->视图名 转换initRequestToViewNameTranslator(context);// 视图解析器initViewResolvers(context);// 重定向参数传递initFlashMapManager(context);
}

组件初始化的逻辑都是一致的,从下文容器查找对应的 Bean、排序并组装 List。
initHandlerMappings()为例:

private void initHandlerMappings(ApplicationContext context) {this.handlerMappings = null;// 是否要在祖先容器检测所有的HandlerMappingif (this.detectAllHandlerMappings) {Map<String, HandlerMapping> matchingBeans =BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);if (!matchingBeans.isEmpty()) {this.handlerMappings = new ArrayList<>(matchingBeans.values());// 排序AnnotationAwareOrderComparator.sort(this.handlerMappings);}}else {try {HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);this.handlerMappings = Collections.singletonList(hm);}catch (NoSuchBeanDefinitionException ex) {// Ignore, we'll add a default HandlerMapping later.}}// 如果一个都没找到,获取默认策略if (this.handlerMappings == null) {this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);if (logger.isTraceEnabled()) {logger.trace("No HandlerMappings declared for servlet '" + getServletName() +"': using default strategies from DispatcherServlet.properties");}}for (HandlerMapping mapping : this.handlerMappings) {if (mapping.usesPathPatterns()) {this.parseRequestPath = true;break;}}
}

至此,DispatcherServlet 就初始化完成了。简单总结一下,因为 DispatcherServlet 本质是一个 Servlet,所以 Servlet 容器启动时会触发它的init()生命周期方法完成初始化。初始化最重要的两个动作,一个是初始化 Web 上下文容器对象、一个是初始化 DispatcherServlet 依赖的各个组件。

处理请求

Servlet 职责是处理请求,Servlet 容器在接收到请求后会调用Servlet#service以响应请求。因为是 HTTP 协议发的请求,所以我们直接看子类 HttpServlet。
为了大家更好的理解,这里画了张流程图:
image.png
HttpServlet 就是单纯的把 ServletRequest 强转成 HttpServletRequest、ServletResponse 强转成 HttpServletResponse,再调用重载方法。

public void service(ServletRequest req, ServletResponse res)throws ServletException, IOException
{HttpServletRequest  request;HttpServletResponse response;if (!(req instanceof HttpServletRequest &&res instanceof HttpServletResponse)) {throw new ServletException("non-HTTP request or response");}request = (HttpServletRequest) req;response = (HttpServletResponse) res;service(request, response);
}

重载方法里,HttpServlet 根据HttpServletRequest#getMethod把请求拆分成了 doGet、doPost、doPut 等方法交给子类处理。

protected void service(HttpServletRequest req, HttpServletResponse resp){String method = req.getMethod();if (method.equals(METHOD_GET)) {doGet(req, resp);}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);}......
}

子类 FrameworkServlet 再把拆分后的方法又聚合到一起,统一交给processRequest()处理,做了几件事:

  • 初始化 LocaleContextHolder、RequestContextHolder,把对象写入 ThreadLocal,方便透传
  • 调用模板方法doService()由子类处理
  • 恢复 ContextHolder 上下文对象
  • 发布请求处理事件 ServletRequestHandledEvent
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {// 初始化XXXContextHolder 把对象写入ThreadLocal,方便透传LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();LocaleContext localeContext = buildLocaleContext(request);RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());initContextHolders(request, localeContext, requestAttributes);try {doService(request, response);}finally {// 恢复上下文resetContextHolders(request, previousLocaleContext, previousAttributes);// 发布处理事件,不管成功与否publishRequestHandledEvent(request, response, startTime, failureCause);}
}

子类 DispatcherServlet 会把一些需要暴露的对象写入到Request#attributes透传,再把请求实际委托给doDispatch()处理。
doDispatch()是个模板方法,处理请求的过程会依赖很多相关组件,主要流程如下:

  • 检查是不是文件上传请求,是的话就把请求转换成 MultipartHttpServletRequest
  • 查找能处理请求的目标处理器 Handler
  • 查找能协调目标 Handler 干活的适配器 HandlerAdapter
  • 触发拦截器方法:HandlerInterceptor#preHandle
  • HandlerAdapter#handle 适配器协调处理器处理请求
  • 触发拦截器方法:HandlerInterceptor#postHandle
  • 处理响应结果 ModelAndView
  • 触发拦截器方法:HandlerInterceptor#afterCompletion
  • 清理文件上传相关的资源
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;try {// 检查文件上传请求 Content-Type:multipart/form-dataprocessedRequest = checkMultipart(request);multipartRequestParsed = (processedRequest != request);// 获取处理器mappedHandler = getHandler(processedRequest);if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;}// 获取处理器适配器,协调Handler处理请求HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// 处理last-modified,如果资源没发生变更,让客户端用缓存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;}}// 触发 HandlerInterceptor#preHandleif (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// 处理请求mv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}applyDefaultViewName(processedRequest, mv);// 触发 HandlerInterceptor#postHandlemappedHandler.applyPostHandle(processedRequest, response, mv);} catch (Exception ex) {dispatchException = ex;} catch (Throwable err) {dispatchException = new NestedServletException("Handler dispatch failed", err);}// 处理结果 对于@ResponseBody接口,只剩触发afterCompletion()processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);} catch (Exception ex) {triggerAfterCompletion(processedRequest, response, mappedHandler, ex);} catch (Throwable err) {triggerAfterCompletion(processedRequest, response, mappedHandler,new NestedServletException("Handler processing failed", err));} finally {if (asyncManager.isConcurrentHandlingStarted()) {if (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}} else {if (multipartRequestParsed) {// 清理文件上传相关的资源cleanupMultipart(processedRequest);}}}
}

检查是不是多文件上传请求,交给了组件 MultipartResolver,判断规则也很简单,检查请求头 ContentType 是不是以 “multipart/form-data” 为前缀:

public boolean isMultipart(HttpServletRequest request) {return StringUtils.startsWithIgnoreCase(request.getContentType(),(this.strictServletCompliance ? MediaType.MULTIPART_FORM_DATA_VALUE : "multipart/"));
}

如果是文件上传请求,会把请求对象转换成 MultipartHttpServletRequest,可以很方便的遍历上传的文件列表。

public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {return new StandardMultipartHttpServletRequest(request, this.resolveLazily);
}

DispatcherServlet#getHandler用来查找目标处理器,依赖组件 HandlerMapping。除了查找 Handler,还会顺带把能应用的拦截器 HandlerInterceptor 一起封装好,构建一个调用链对象 HandlerExecutionChain。

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {if (this.handlerMappings != null) {for (HandlerMapping mapping : this.handlerMappings) {HandlerExecutionChain handler = mapping.getHandler(request);if (handler != null) {return handler;}}}return null;
}

HandlerMapping#getHandlerAdapter用来查找处理器适配器,在 Spring MVC 里面,处理器 Handler 用 Object 表示,可以以任何形式存在。为了协调 Handler 干活,还必须找到对应的 HandlerAdapter。
DispatcherServlet 初始化会加载容器内所有的 HandlerAdapter 类型的 Bean 封装成 List,查找的过程就是按顺序遍历是否支持目标 Handler,找到了就直接返回。

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {if (this.handlerAdapters != null) {for (HandlerAdapter adapter : this.handlerAdapters) {// 适配器是否支持目标handlerif (adapter.supports(handler)) {return adapter;}}}
}

找到了目标 Handler 和 HandlerAdapter,先触发前置拦截器:HandlerInterceptor#preHandle,再调用HandlerAdapter#handle把请求交给目标处理器处理,这一步才是真正的核心,也很复杂,会另起篇幅分析。请求处理完后,触发后置拦截器:HandlerInterceptor#postHandle,最后处理 ModelAndView 渲染视图,触发拦截器:HandlerInterceptor#afterCompletion
最后的最后,整个请求处理完毕后,如果是文件上传请求,还需要调用cleanupMultipart()清理资源。

尾巴

DispatcherServlet 本质是个 Servlet,Servlet 容器启动会触发其init生命周期方法做初始化,初始化最重要的两件事分别是初始化 Web 上下文容器对象、以及依赖的各种解析器,映射器等组件。
Servlet 容器收到请求会调用Servlet#service响应请求,HttpServlet 会根据方法类型把请求拆分成对应的 doXXX 方法由子类处理,FrameworkServlet 再把请求聚合起来,最终由DispatcherServlet#doDispatch处理。这是个模板方法,先检查是不是文件上传请求,再找到目标处理器 Handler、再查找适配器 HandlerAdapter、让 HandlerAdapter 协调 Handler 处理请求,最终处理 ModelAndView 渲染视图。
DispatcherServlet 本身并不复杂,它只是定义了一个处理请求的算法骨架,处理的细节交给了对应的组件类,这也很负责单一职责原则。
通过 DispatcherServlet 我们已经了解了 Spring MVC 处理请求的整体流程,但是请求处理的细节,还需要深入到其依赖的各个组件类。

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

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

相关文章

进阶学习——Linux系统中重点‘进程’

目录 一、程序和进程的关系 1.程序 2.进程 2.1线程 2.2协程 3.进程与线程的区别 4.总结 4.1延伸 5.进程使用内存的问题 5.1内存泄漏——Memory Leak 5.2内存溢出——Memory Overflow 5.3内存不足——OOM&#xff08;out of memory&#xff09; 5.4进程使用内存出现…

leecode | 每日温度

leecode 739 给定一个整数数组 temperatures &#xff0c;表示每天的温度&#xff0c;返回一个数组 answer &#xff0c;其中 answer[i] 是指对于第 i 天&#xff0c;下一个更高温度出现在几天后。如果气温在这之后都不会升高&#xff0c;请在该位置用 0 来代替。 这种在同一个…

如何正确使用docker搭建靶场--pikachu

在Linux中搭建靶场——pikachu 1.开启docker systemctl start docker 2.查看docker状态 systemctl status docker 3.查看docker存在那些镜像 docker images 4.拉取镜像&#xff0c;这里是以pikachu为例因此需要一个php5的版本 &#xff08;1&#xff09;打开代理&#xff…

【Nodejs】基于Promise异步处理的博客demo代码实现

目录 package.json www.js db.js app.js routes/blog.js controllers/blog.js mysql.js responseModel.js 无开发&#xff0c;不安全。 这个demo项目实现了用Promise异步处理http的GET和POST请求&#xff0c;通过mysql的api实现了博客增删改查功能&#xff0c;但因没有…

为什么亚马逊卖家一定要有独立站?新手低成本快速搭建跨境电商独立站完整图文教程

目录 前言&#xff1a;为什么亚马逊卖家一定要有独立站&#xff1f; 为什么不选Shopify建站&#xff1f; 效果展示 一、购买域名 二、购买主机托管 三、搭建网站 前言&#xff1a;为什么亚马逊卖家一定要有独立站&#xff1f; 最近不少卖家朋友来问独立站建站方面的问题…

桥接模式和NAT模式的区别

一、桥接模式(bridged networking) 在桥接模式下,VMWare虚拟出来的操作系统就像是局域网中一台独立的主机,它能够访问网内任何一台机器。 在桥接模式下,你必须手工为虚拟系统配置IP地址、子网掩码,并且还要和宿主机器处于同一网段,这样虚拟系统才能和宿主机器进行通信。 配置…

安全防御之授权和访问控制技术

授权和访问控制技术是安全防御中的重要组成部分&#xff0c;主要用于管理和限制对系统资源&#xff08;如数据、应用程序等&#xff09;的访问。授权控制用户可访问和操作的系统资源&#xff0c;而访问控制技术则负责在授权的基础上&#xff0c;确保只有经过授权的用户才能访问…

前端插件库-VUE3 使用 vue-codemirror 插件

VUE3 插件 vue-codemirror 使用步骤和实例、基于 CodeMirror &#xff0c;适用于 Vue 的 Web 代码编辑器。 第一步&#xff1a;安装 vue-codemirror & codemirror 包 &#xff0c; 以及语言包 npm install codemirror --save npm install vue-codemirror --savenpm insta…

VS2022 创建windows服务-Windows Service

vs2022 2023等版本出现&#xff0c;似乎被忘记的早期的Windows Service服务是如何创建的呢&#xff1f;本文介绍了如何用新版本VS进行C#创建、安装、启动、监控、卸载简单的Windows Service 的内容步骤和注意事项。windows服务可以在windows中自动运行。 一、创建一个Windows …

Android 多线程简单使用

在Android中&#xff0c;可以使用Java的Thread类或者使用AsyncTask类来实现多线程功能。 使用Thread类实现多线程&#xff1a; public class MyThread extends Thread {Overridepublic void run() {// 在这里执行多线程任务// 可以通过调用Thread的静态方法sleep()模拟耗时操…

若依 参数验证、数据校验使用

参数验证 spring boot中可以用@Validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。 1、基础使用 因为spring boot已经引入了基础包,所以直接使用就可以了。首先在controller上声明@Validated需要对数据进行校验。 public AjaxResult add(@Validate…

基于价值认同的需求侧电能共享分布式交易策略(matlab完全复现)

目录 1 主要内容 2 部分程序 3 程序结果 4 下载链接 1 主要内容 该程序完全复现《基于价值认同的需求侧电能共享分布式交易策略》&#xff0c;针对电能共享市场的交易机制进行研究&#xff0c;提出了基于价值认同的需求侧电能共享分布式交易策略&#xff0c;旨在降低电力市…

基于SpringBoot的旅游网站281

文章目录 项目介绍主要功能截图:部分代码展示设计总结项目获取方式🍅 作者主页:超级无敌暴龙战士塔塔开 🍅 简介:Java领域优质创作者🏆、 简历模板、学习资料、面试题库【关注我,都给你】 🍅文末获取源码联系🍅 项目介绍 基于SpringBoot的旅游网站281,java项目。…

电锯切割狂

欢迎来到程序小院 电锯切割狂 玩法&#xff1a;把木块切成等分的碎片&#xff0c;每关都会有切割次数&#xff0c;木块数&#xff0c;切割越均匀分数越搞&#xff0c; 有简单、正常、困难、专家版&#xff0c;快去解锁不同版本进行切割吧^^。开始游戏https://www.ormcc.com/pl…

你vue有写过自定义指令吗?知道自定义指令的应用场景有哪些吗?

一、什么是指令 开始之前我们先学习一下指令系统这个词 指令系统是计算机硬件的语言系统&#xff0c;也叫机器语言&#xff0c;它是系统程序员看到的计算机的主要属性。因此指令系统表征了计算机的基本功能决定了机器所要求的能力 在vue中提供了一套为数据驱动视图更为方便的…

MySQL的基础架构之内部执行过程

MySQL的逻辑架构图 如上图所示&#xff0c;MySQL可以分为Server层和存储引擎层两部分&#xff1a; 1&#xff09;Server层涵盖了MySQL的大多数核心服务功能&#xff0c;以及所有的内置函数&#xff08;如日期、时间、数学和加密函数等&#xff09;&#xff0c;所有跨存储引擎…

linux新M2固态挂载

一、普通挂载 查看硬盘信息 sudo fdisk -l创建文件系统 sudo mkfs.ext4 /dev/nvme0n1创建挂载点 sudo mkdir /home/zain挂载 sudo mount /dev/nvme0n1 /home/zain二、永久挂载 vi /etc/fstabinsert&#xff1a; /dev/nvme0n1 /home/zain ext4 defaults 0 2 wqs…

知识笔记(六十九)———缓冲区溢出攻击

1. 什么是缓冲区溢出 &#xff08;1&#xff09;缓冲区 缓冲区是一块连续的计算机内存区域&#xff0c;用于在将数据从一个位置移到另一位置时临时存储数据。这些缓冲区通常位于 RAM 内存中&#xff0c;可保存相同数据类型的多个实例&#xff0c;如字符数组。 计算机经常使用…

3.10 Android eBPF HelloWorld调试(四)

一,读取eBPF map的android应用程序示例 1.1 C++源码及源码解读 /system/memory/bpfmapparsed/hello_world_map_parser.cpp //基于aosp android12#define LOG_TAG "BPF_MAP_PARSER"#include <log/log.h> #include <stdlib.h> #include <unistd.h&g…

Day22 112路径总和 113路径总和II 106中后构造二叉树/中前构造二叉树 654最大二叉树

给定一个二叉树和一个目标和&#xff0c;判断该树中是否存在根节点到叶子节点的路径&#xff0c;这条路径上所有节点值相加等于目标和。 递归&#xff1a; 可以采用深度优先的递归方式&#xff0c;前中后序都可以&#xff08;因为中节点没有处理逻辑&#xff09;。首先确定参…