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进程使用内存出现…

如何正确使用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; 最近不少卖家朋友来问独立站建站方面的问题…

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

授权和访问控制技术是安全防御中的重要组成部分&#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 …

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

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

电锯切割狂

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

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

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

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

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

放大镜Scratch-第14届蓝桥杯Scratch省赛真题第3题

3. 放大镜&#xff08;50分&#xff09; 评判标准&#xff1a; 10分&#xff1a;满足"具体要求"中的1&#xff09;&#xff1b; 15分&#xff1a;满足"具体要求"中的2&#xff09;&#xff1b; 25分&#xff0c;满足"具体要求"中的3&#xff…

C#高级:Lambda表达式分组处理2(WITH ROLLUP关键字)

目录 一、问题引入 二、with rollup查询 三、去掉多余数据 四、拓展 一、问题引入 查询SQL后结果如下&#xff0c;字段分别是用户、项目、批次、工作时间&#xff1a; SELECT UserID,ProjectID,ProBatchesID,WorkHour FROM MAINTABLE GROUP BY HourFiller ,ProjectID ,…

【ESP32接入国产大模型之文心一言】

1. 怎样接入文心一言 随着人工智能技术的不断发展&#xff0c;自然语言处理领域也得到了广泛的关注和应用。在这个领域中&#xff0c;文心一言作为一款强大的自然语言处理工具&#xff0c;具有许多重要的应用价值。本文将重点介绍如何通过ESP32接入国产大模型之文心一言api&am…

图片中src属性绑定不同的路径

vue3 需求是按钮disable的时候&#xff0c;显示灰色的icon&#xff1b;非disable状态&#xff0c;显示白色的icon 一开始src写成三元表达式&#xff0c;发现不行&#xff0c;网上说src不能写成三元表达式&#xff0c;vue会识别成字符串 最后的解决方案 同时&#xff0c;发现…

在Cadence中单独添加或删除器件与修改网络的方法

首先需要在设置中使能 ,添加或修改逻辑选项。 添加或删除器件&#xff0c;点击logic-part&#xff0c;选择需要添加或删除的器件&#xff0c;这里的器件必须是PCB中已经有的器件&#xff0c;Refdes中输入添加或删除的器件标号&#xff0c;点击Add添加。 添加完成后就会显示在R1…

基于springboot智慧食堂管理系统源码和论文

随着Internet的发展&#xff0c;人们的日常生活已经离不开网络。未来人们的生活与工作将变得越来越数字化&#xff0c;网络化和电子化。网上管理&#xff0c;它将是直接管理“智慧食堂”系统的最新形式。本论文是以构建“智慧食堂”系统为目标&#xff0c;使用java技术制作&…

odoo17 | 基本视图

前言 我们在上一章中已经看到Odoo能够为给定模型生成默认视图。在实践中&#xff0c;默认视图是绝对不可接受的用于商业应用程序。相反&#xff0c;我们至少应该以逻辑方式组织各种字段。 视图在带有动作和菜单的XML文件中定义。它们是ir.ui.view模型的实例。 在我们的房地产…

OpenHarmony从入门到放弃(一)

OpenHarmony从入门到放弃&#xff08;二&#xff09; 一、OpenHarmony的基本概念和特性 OpenHarmony是由开放原子开源基金会孵化及运营的开源项目&#xff0c;其目标是构建一个面向全场景、全连接、全智能的时代的智能终端设备操作系统。 分布式架构 OpenHarmony采用分布式…

Termius for Mac/Win:一款功能强大的终端模拟器、SSH 和 SFTP 客户端软件

随着远程工作和云技术的普及&#xff0c;对于高效安全的远程访问和管理服务器变得至关重要。Termius&#xff0c;一款强大且易用的终端模拟器、SSH 和 SFTP 客户端软件&#xff0c;正是满足这一需求的理想选择。 Termius 提供了一站式的解决方案&#xff0c;允许用户通过单一平…