【SSM】DispatcherServlet详解

2023-11-16

功能

SpringMVC的核心就是DispatcherServlet,DispatcherServlet实质也是一个HttpServlet。DispatcherSevlet负责将请求分发,所有的请求都有经过它来统一分发。

大致看下SpringMVC请求处理的流程:
在这里插入图片描述
 用户向服务器发送请求,请求会到DispatcherServlet,DispatcherServlet 对请求URL进行解析,得到请求资源标识符(URI), 然后根据该URI,调用HandlerMapping获得该Handler(就是控制层)配置的所有相关的对象(包括一个Handler处理器对象、多个HandlerInterceptor拦截器对象),最后以HandlerExecutionChain对象的形式返回。
  DispatcherServlet根据获得的Handler,选择一个合适的HandlerAdapter。提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)。 在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:

  • HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息
  • 数据转换:对请求消息进行数据转换。如String转换成Integer、Double等
  • 数据格式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等
  • 数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中

Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象根据返回的ModelAndView,选择一个适合的ViewResolver返回给DispatcherServlet;ViewResolver 结合Model和View,来渲染视图,最后将渲染结果返回给客户端。

原理解析

DispatcherSevlet中最重要的是doService方法:

    //获取请求,设置一些request的参数,然后分发给doDispatch
	@Override
	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
		if (logger.isDebugEnabled()) {
			String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
			logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
					" processing " + request.getMethod() + " request for [" + getRequestUri(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<String, Object>();
			Enumeration<?> attrNames = request.getAttributeNames();
			while (attrNames.hasMoreElements()) {
				String attrName = (String) attrNames.nextElement();
				if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {
					attributesSnapshot.put(attrName, request.getAttribute(attrName));
				}
			}
		}
 
		// Make framework objects available to handlers and view objects.
		/* 设置web应用上下文**/
		request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
		/* 国际化本地**/
		request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
		/* 样式**/
		request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
		//设置样式资源
		request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
		//请求刷新时保存属性
		FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
		if (inputFlashMap != null) {
			request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
		}
		//Flash attributes 在对请求的重定向生效之前被临时存储(通常是在session)中,并且在重定向之后被立即移除
		request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
		//FlashMap 被用来管理 flash attributes 而 FlashMapManager 则被用来存储,获取和管理 FlashMap 实体.
		request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
 
		try {
			doDispatch(request, response);
		}
		finally {
			if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
				// Restore the original attribute snapshot, in case of an include.
				if (attributesSnapshot != null) {
					restoreAttributesAfterInclude(request, attributesSnapshot);
				}
			}
		}
	}

doService方法对Request设置了一些全局属性,最终接下来的操作是在doDispatcher:

     /**
	 *将Handler进行分发,handler会被handlerMapping有序的获得
	 *通过查询servlet安装的HandlerAdapters来获得HandlerAdapters来查找第一个支持handler的类
	 *所有的HTTP的方法都会被这个方法掌控。取决于HandlerAdapters 或者handlers 他们自己去决定哪些方法是可用
	 *@param request current HTTP request
	 *@param response current HTTP response
	 */
	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		/* 当前HTTP请求**/
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;
 
		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
 
		try {
			ModelAndView mv = null;
			Exception dispatchException = null;
 
			try {
				//判断是否有文件上传
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);
 
				// getHandler中遍历HandlerMapping,获取对应的HandlerExecutionChain
                // HandlerExecutionChain,包含HandlerIntercrptor和HandlerMethod
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null || mappedHandler.getHandler() == null) {
					noHandlerFound(processedRequest, response);
					return;
				}
 
				
				//获得HandlerAdapter
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
 
				//获得HTTP请求方法
				String method = request.getMethod();
				boolean isGet = "GET".equals(method);
				if (isGet || "HEAD".equals(method)) {
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (logger.isDebugEnabled()) {
						logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
					}
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}
				//如果有拦截器的话,会执行拦截器的preHandler方法
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}
 
				//返回ModelAndView
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
 
				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}
				//当view为空时,,根据request设置默认view
				applyDefaultViewName(processedRequest, mv);
				//执行拦截器的postHandle
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
				dispatchException = ex;
			}
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Error err) {
			triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
		}
		finally {
			//判断是否是异步请求
			if (asyncManager.isConcurrentHandlingStarted()) {
				// Instead of postHandle and afterCompletion
				if (mappedHandler != null) {
					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				}
			}
			else {
				// Clean up any resources used by a multipart request.
				//删除上传资源
				if (multipartRequestParsed) {
					cleanupMultipart(processedRequest);
				}
			}
		}
	}

HandlerInterceptor拦截器

在doDispatcher方法中:

mappedHandler = getHandler(processedRequest);

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
   for (HandlerMapping hm : this.handlerMappings) {
      if (logger.isTraceEnabled()) {
         logger.trace(
               "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
      }
      HandlerExecutionChain handler = hm.getHandler(request);
      if (handler != null) {
         return handler;
      }
   }
   return null;
}

遍历HandlerMapping,获取对应的HandlerExecutionChain,HandlerExecutionChain中有3个变量:

1private final Object handler; // HandlerMethod
2private HandlerInterceptor[] interceptors; //所有的HandlerInterceptor的数组
3private List<HandlerInterceptor> interceptorList; //所有的HandlerInterceptor的链表

在执行Controller的方法调用之前先执行:

if (!mappedHandler.applyPreHandle(processedRequest, response)) {
   return;
}

HandlerExecutionChain#applyPreHandle即调用HandlerInterceptor# preHandler方法。在Controller的方法调用之后:

mappedHandler.applyPostHandle(processedRequest, response, mv);

HandlerExecutionChain#applyPostHandle即调用HandlerInterceptor# postHandle方法。最后

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

方法会调用:

mappedHandler.triggerAfterCompletion(request, response, null);

调用HandlerInterceptor#afterCompletion方法。

HandlerExceptionResolver异常处理

在doDispatcher方法中会catch住Controller方法调用的异常,之后再处理:

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
      HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {

   boolean errorView = false;

   if (exception != null) {
      if (exception instanceof ModelAndViewDefiningException) {
         logger.debug("ModelAndViewDefiningException encountered", exception);
         mv = ((ModelAndViewDefiningException) exception).getModelAndView();
      }
      else {
         Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
         mv = processHandlerException(request, response, handler, exception);
         errorView = (mv != null);
      }
   }
   // …
}

如果有exception,调用processHandlerException:

protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
      Object handler, Exception ex) throws Exception {

   // Check registered HandlerExceptionResolvers...
   ModelAndView exMv = null;
   for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
      exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
      if (exMv != null) {
         break;
      }
   }
   if (exMv != null) {
      if (exMv.isEmpty()) {
         request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
         return null;
      }
      // We might still need view name translation for a plain error model...
      if (!exMv.hasView()) {
         exMv.setViewName(getDefaultViewName(request));
      }
      if (logger.isDebugEnabled()) {
         logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex);
      }
      WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
      return exMv;
   }

   throw ex;
}

SpringMVC启动加载时会将所有的异常处理HandlerExceptionResolver实现类存放到handlerExceptionResolvers中,当默认执行时,只要返回的exMv不为空就中止执行其他的异常处理实现类。
  可以实现多个HandlerExceptionResolver的异常处理类,执行顺序由其order属性决定, order值越小,越是优先执行, 在执行到第一个返回不是null的ModelAndView的Resolver时,不再执行后续的尚未执行的Resolver的异常处理方法。

HandlerMethodArgumentResolver参数解析器

HandlerMethodArgumentResolver是用来为处理器解析参数的,主要用在HandlerMethod中,每个Resolver对应一种类型的参数,其实现类特别的多。HandlerMethodArgumentResolver的接口定义如下:

  • supportsParameter 用于判断是否支持对某种参数的解析
  • resolveArgument 将请求中的参数值解析为某种对象

参数的处理是在InvocableHandlerMethod类中,当执行具体的请求处理时执行invokeForRequest:

public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,
      Object... providedArgs) throws Exception {
   //获取执行Controller的函数的参数对象
   Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
   if (logger.isTraceEnabled()) {
      logger.trace("Invoking '" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +
            "' with arguments " + Arrays.toString(args));
   }
   Object returnValue = doInvoke(args);
   if (logger.isTraceEnabled()) {
      logger.trace("Method [" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +
            "] returned [" + returnValue + "]");
   }
   return returnValue;
}

具体的参数解析器的选择和使用参数解析器去解析参数的实现都在getMethodArgumentValues中。SpringMVC在启动时会将所有的参数解析器放到HandlerMethodArgumentResolverComposite,然后从HandlerMethodArgumentResolverComposite参数解析器集合中选择一个支持对parameter解析的参数解析器,之后就使用支持参数解析的解析器进行参数解析。

private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
		//获取执行的具体函数的参数
		MethodParameter[] parameters = getMethodParameters();
		Object[] args = new Object[parameters.length];
		for (int i = 0; i < parameters.length; i++) {
			MethodParameter parameter = parameters[i];
			parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
			GenericTypeResolver.resolveParameterType(parameter, getBean().getClass());
			//获取参数值对象
			args[i] = resolveProvidedArgument(parameter, providedArgs);
			if (args[i] != null) {
				continue;
			}
			//首先判断是否有参数解析器支持参数parameter,采用职责链的设计模式
			if (this.argumentResolvers.supportsParameter(parameter)) {
				try {
					//如果参数解析器支持解析参数parameter,那么解析参数成Controller的函数需要的格式
					args[i] = this.argumentResolvers.resolveArgument(
							parameter, mavContainer, request, this.dataBinderFactory);
					continue;
				}
				catch (Exception ex) {
					if (logger.isDebugEnabled()) {
						logger.debug(getArgumentResolutionErrorMessage("Error resolving argument", i), ex);
					}
					throw ex;
				}
			}
			if (args[i] == null) {
				String msg = getArgumentResolutionErrorMessage("No suitable resolver for argument", i);
				throw new IllegalStateException(msg);
			}
		}
		//返回参数值
		return args;
	}

以上代码会将URL链接提交的参数值转换为对象值。

HandlerMethodReturnValueHandler返回值解析器

作用:HandlerMethodReturnValueHandler是用于对Controller中函数执行的返回值进行处理操作的。接口HandlerMethodReturnValueHandler定义的抽象方法如下:

  • supportsReturnType用于判断是否支持对返回值的处理。
  • handleReturnValue实现对返回值的处理操作。

执行过程是在ServletInvocableHandlerMethod的invokeAndHandle中:

public void invokeAndHandle(ServletWebRequest webRequest,
			ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
		// 获取返回值
		Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
		.......
		//对返回值进行处理操作
		this.returnValueHandlers.handleReturnValue(
					returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
		.........
	}

具体实现是在HandlerMethodReturnValueHandlerComposite中,HandlerMethodReturnValueHandlerComposite中包含了所有springMVC提供的返回值处理器。

public void handleReturnValue(Object returnValue, MethodParameter returnType,
      ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
   //选择可以处理返回值的处理器
   HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
   if (handler == null) {
      throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
   }
    //处理返回值
   handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}

private HandlerMethodReturnValueHandler selectHandler(Object value, MethodParameter returnType) {
   boolean isAsyncValue = isAsyncReturnValue(value, returnType);
   for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
      if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
         continue;
      }
      if (handler.supportsReturnType(returnType)) {
         return handler;
      }
   }
   return null;
}



转载自大佬
zero__007


转载只为学习,记录,回顾
无商业行为

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【SSM】DispatcherServlet详解 的相关文章

随机推荐

  • 如何在 Windows Server 上搭建 Git Repository Server?

    Git 作为开发工具之一 主要用于辅助团队开发的版本控制等 相似的工具大家也或许接触过 CVS 和 SVN 等 最为大家耳熟能详的或许就是 Github 和中国的 Gitee 了 但是网上的资料都围绕着 Linux 的环境 而没有过多考虑过
  • SVF——C/C++指针分析/(数据)依赖分析框架

    这篇文章包括 SVF介绍 SVF源码解读 SVF优势与不足 如何扩展改进 文章包括一些个人观点 若觉得有误请留言纠正 感谢 在这篇文章之前强烈推荐看我公众号之前推的一篇文章 CG0 2011 Flow sensitive pointer a
  • 虚拟机配置时间同步-ntp

    安装ntp yum y install ntp 验证是否安装成功 ntpd version 依次执行以下命令即可 ntpdate u ntp sjtu edu cn cp usr share zoneinfo Asia Shanghai e
  • 7 款炫酷的 VSCode 主题扩展

    关注后回复 进群 拉你进程序员交流群 作者丨小集 来源丨小集 ID zsxjtip 在 VSCode 中 安装自定义主题和图标包可以彻底改变 VSCode 的外观 VSCode 有数千种不同的包可用 在这里 我们推荐几个不错的主题扩展 Gi
  • 这是啥SQL,室友看了人傻了

    文章目录 SQLite适应常规基本应用场景 SQLite面对复杂场景尚有不足 SPL全面支持各种数据源 SPL的计算能力更强大 优化体系结构 SPL资料 可以在Java应用中嵌入的数据引擎看起来比较丰富 但其实并不容易选择 Redis计算能
  • 【数据结构】五种用于查询的数据结构 性能测试

    github项目地址 1 总体说明 本报告一共实现了五种用于查询的数据结构 二叉搜索树 二叉平衡树 二叉伸展树 跳表 数组 在完成各种数据的增删查功能的基础上 对于不同数据结构的查询效率进行了评测与对比 对空间性能进行了理论的分析 大致实验
  • AngularJS UI Router(ui.router)嵌套视图(Nested Views)

    1 dom结构 index html
  • UE_移动端测试使用

    教程流程 参照官方文档 android篇 https docs unrealengine com 5 1 zh CN android development requirements for unreal engine https docs
  • 电巢携手陕西理工大学“硬件研发岗位岗前项目实训”顺利开班!

    为深化校企合作 产教融合助力新工科建设 提升学生工程实践能力 电巢工程能力实训班按照不同岗位类别 匹配对应的企业岗位任职能力要求对学生开展分级培养 以产业需求为导向 培养创新型 应用型人才 7月27日下午3时 深圳电巢联合陕西理工大学物理与
  • dc-3 靶机渗透学习

    靶机修复 dc 3靶机可能会存在扫不到靶机ip的问题 可以参考下面这篇博客解决 编辑网卡配置文件时命令有点错误 vim etc network interfacers 改成 vim etc network interfaces Vulnhu
  • 【SpringCloudAlibaba】Nacos服务注册和配置中心配合nginx负载

    文章目录 概述 注册中心 POM YML 启动类 CAP 配置中心 POM YML 启动类 ConfigClientController Nacos中的匹配规则 三种方案加载配置 示例 集群部署 概述 部署模式 修改derby为mysql
  • Fiddler抓包工具配置+Jmeter基本使用

    目录 一 Fiddler抓包工具的配置和使用 局域网络配置 Fiddler配置 Fiddler抓包实例 二 Jmeter的基本使用 Jmeter的安装配置 第一个Jmeter脚本 一 Fiddler抓包工具的配置和使用 在编写网关自动化脚本
  • GCC入门详解

    一 基本概念 gcc编译源程序分为4个阶段 预处理 编译 汇编 链接 1 预处理阶段 将头文件的内容插入到源代码中 替换宏定义 去掉注释等 预处理后的文件后缀名为 i 2 编译阶段 编译器将预处理后的文件翻译成汇编代码文件 后缀名为 s 3
  • uni-app 连接逍遥模拟器 安卓模拟器 不显示 找不到 端口映射

    最近公司为了让我全面发展 给了一个小活练练手 由于Android和ios开发的小伙伴比较忙 我被拉来开发一个App 由于需要多端使用 最后选择使用uni app 来开发 刚开始都是在h5页面来调试 最后测试App的时候需要使用安卓模拟器来调
  • GameMode问题

    GameMode问题 1 缘由 初始化了两个关卡 一个登录关卡 一个内容关卡 配置了两个GameMode 分别在关卡中设置好了GameMode 通过调用OpenLevel实现关卡跳转 如下图 然 运行过程中 关卡完成了跳转 进入内容关卡后G
  • MusicGen一键音乐风格迁移

    想象一下 您可以随心所欲地创作轻快的乡村曲风 缠绵的蓝调 史诗般的管弦乐 视频BGM创作之路上 再也不会有任何阻碍 01 什么是MusicGen Meta MusicGen建立在强大的Transformer模型的基础上 追随ChatGPT等
  • Pcshare远控源码偏重分析(一)

    0x00背景 PcShare是一款功能强大的远程管理软件 可以在内网 外网任意位置随意管理需要的远程主机 该软件是由国内安全爱好者无可非议开发 在当时这款远控在大家应该比较熟悉了 VC编译器调出来的的小体积全功能木马 相比Delphi的灰鸽
  • Linux权限

    一 Linux权限的概念 Linux下有两种用户 超级用户 root 普通用户 超级用户 可以再linux系统下做任何事情 不受限制 普通用户 在linux下做有限的事情 超级用户的命令提示符是 普通用户的命令提示符是 1 1 用户间的切换
  • 最优清零方案python(蓝桥杯)

    1 问题描述 给定一个长度为 N 的数列 A1 A2 AN 现在小蓝想通过若干次操作将 这个数列中每个数字清零 每次操作小蓝可以选择以下两种之一 选择一个大于 0 的整数 将它减去 1 选择连续 K 个大于 0 的整数 将它们各减去 1 小
  • 【SSM】DispatcherServlet详解

    功能 SpringMVC的核心就是DispatcherServlet DispatcherServlet实质也是一个HttpServlet DispatcherSevlet负责将请求分发 所有的请求都有经过它来统一分发 大致看下Spring