spring-web InvocableHandlerMethod 源码分析

news2024/12/29 2:32:03

说明

  1. 本文基于 jdk 8, spring-framework 5.2.x 编写。
  2. @author JellyfishMIX - github / blog.jellyfishmix.com
  3. LICENSE GPL-2.0

类层次

HandlerMethod

HandlerMethod,处理器的方法的封装对象。HandlerMethod 只提供了处理器的方法的基本信息,不提供调用逻辑。

InvocableHandlerMethod,继承 HandlerMethod 类,可调用的 HandlerMethod 实现类。

ServletInvocableHandlerMethod#invokeAndHandle 方法

org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle

核心方法,处理请求。

  1. 执行调用处理请求,设置 responseStatus。
  2. 如果 returnValue 为空,设置 ModelAndViewContainer 为请求已处理然后 return,和 @ResponseStatus 注解相关。
  3. 设置 ModelAndViewContainer 为请求未处理,通过 HandlerMethodReturnValueHandlerComposite(HandlerMethodReturnValueHandler) 处理返回值。
public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
    @Nullable
	private HandlerMethodReturnValueHandlerComposite returnValueHandlers;
    
	public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
		// 执行调用处理请求
		Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
		// 设置 responseStatus
		setResponseStatus(webRequest);

		// 如果 returnValue 为空,设置 ModelAndViewContainer 为请求已处理然后 return,和 @ResponseStatus 注解相关
		if (returnValue == null) {
			if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
				disableContentCachingIfNecessary(webRequest);
				mavContainer.setRequestHandled(true);
				return;
			}
		}
		else if (StringUtils.hasText(getResponseStatusReason())) {
			mavContainer.setRequestHandled(true);
			return;
		}

		// 设置 ModelAndViewContainer 为请求未处理
		mavContainer.setRequestHandled(false);
		Assert.state(this.returnValueHandlers != null, "No return value handlers");
		try {
			// 通过 HandlerMethodReturnValueHandlerComposite(HandlerMethodReturnValueHandler) 处理返回值
			this.returnValueHandlers.handleReturnValue(
					returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
		}
		catch (Exception ex) {
			if (logger.isTraceEnabled()) {
				logger.trace(formatErrorForReturnValue(returnValue), ex);
			}
			throw ex;
		}
	}
}

ServletInvocableHandlerMethod#setResponseStatus 方法

org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#setResponseStatus

调用 setResponseStatus(ServletWebRequest webRequest) 设置响应的状态码。

  1. 获得 status,和 @ResponseStatus 注解相关。
  2. 设置响应的状态码。有 reason,则设置 status + reason。无 reason,仅设置 status。
  3. RedirectView 相关。
	private void setResponseStatus(ServletWebRequest webRequest) throws IOException {
		// 获得 status,和 @ResponseStatus 注解相关
		HttpStatus status = getResponseStatus();
		if (status == null) {
			return;
		}

		// 设置响应的状态码
		HttpServletResponse response = webRequest.getResponse();
		if (response != null) {
			String reason = getResponseStatusReason();
			// 有 reason,则设置 status + reason
			if (StringUtils.hasText(reason)) {
				response.sendError(status.value(), reason);
			}
			else {
				// 无 reason,仅设置 status
				response.setStatus(status.value());
			}
		}

		// To be picked up by RedirectView
		// RedirectView 相关
		webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, status);
	}

InvocableHandlerMethod

InvocableHandlerMethod#invokeForRequest 方法 – 执行调用处理请求

org.springframework.web.method.support.InvocableHandlerMethod#invokeForRequest

执行调用处理请求。

	
	@Nullable
	public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
		// 解析参数
		Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
		if (logger.isTraceEnabled()) {
			logger.trace("Arguments: " + Arrays.toString(args));
		}
		// 执行调用
		return doInvoke(args);
	}

InvocableHandlerMethod#getMethodArgumentValues 方法 – 解析参数

org.springframework.web.method.support.InvocableHandlerMethod#getMethodArgumentValues

  1. 获得方法的参数。
  2. 遍历方法参数,将参数解析成对应的类型。
    1. 获得当前遍历的 MethodParameter 对象,给它设置 parameterNameDiscoverer。
    2. 从 providedArgs 中获得参数。如果获得到,则进入下一个参数的解析,默认情况 providedArgs 不会传参。
    3. 判断 resolvers 是否支持当前的参数解析。
    4. 执行参数解析,解析成功后进入下一个参数的解析。
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
		// 获得方法的参数
		MethodParameter[] parameters = getMethodParameters();
		// 无参,返回空数组
		if (ObjectUtils.isEmpty(parameters)) {
			return EMPTY_ARGS;
		}

		// 遍历方法参数,将参数解析成对应的类型
		Object[] args = new Object[parameters.length];
		for (int i = 0; i < parameters.length; i++) {
			// 获得当前遍历的 MethodParameter 对象,给它设置 parameterNameDiscoverer
			MethodParameter parameter = parameters[i];
			parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
			// 从 providedArgs 中获得参数。如果获得到,则进入下一个参数的解析,默认情况 providedArgs 不会传参。
			args[i] = findProvidedArgument(parameter, providedArgs);
			if (args[i] != null) {
				continue;
			}
			// 判断 resolvers 是否支持当前的参数解析
			if (!this.resolvers.supportsParameter(parameter)) {
				throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
			}
			try {
				// 执行参数解析,解析成功后进入下一个参数的解析
				args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
			}
			catch (Exception ex) {
				// Leave stack trace for later, exception may actually be resolved and handled...
				if (logger.isDebugEnabled()) {
					String exMsg = ex.getMessage();
					if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
						logger.debug(formatArgumentError(parameter, exMsg));
					}
				}
				throw ex;
			}
		}
		return args;
	}

InvocableHandlerMethod#doInvoke 方法

org.springframework.web.method.support.InvocableHandlerMethod#doInvoke

  1. 设置方法为可访问。
  2. 通过反射执行方法调用。BridgedMethod 基于泛型安全为了和 jdk 1.5 之前的字节码兼容,无需关注 BridgedMethod。
	@Nullable
	protected Object doInvoke(Object... args) throws Exception {
		// 设置方法为可访问
		ReflectionUtils.makeAccessible(getBridgedMethod());
		try {
			// 通过反射执行方法调用。BridgedMethod 基于泛型安全为了和 jdk 1.5 之前的字节码兼容,无需关注 BridgedMethod
			return getBridgedMethod().invoke(getBean(), args);
		}
		catch (IllegalArgumentException ex) {
			assertTargetBean(getBridgedMethod(), getBean(), args);
			String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");
			throw new IllegalStateException(formatInvokeError(text, args), ex);
		}
		catch (InvocationTargetException ex) {
			// Unwrap for HandlerExceptionResolvers ...
			Throwable targetException = ex.getTargetException();
			if (targetException instanceof RuntimeException) {
				throw (RuntimeException) targetException;
			}
			else if (targetException instanceof Error) {
				throw (Error) targetException;
			}
			else if (targetException instanceof Exception) {
				throw (Exception) targetException;
			}
			else {
				throw new IllegalStateException(formatInvokeError("Invocation failure", args), targetException);
			}
		}
	}

后言

  1. 本文可以看到很重要的两个组件 HandlerMethodArgumentResolver 用于获取请求参数,HandlerMethodReturnValueHandler 用于处理返回值,后续分析。

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

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

相关文章

大数据面试小抄

项目地址&#xff1a;https://github.com/GTyingzi/BigDATA 该项目是自己在学习大数据过程中整理、总结下来的一份面试小抄。涵盖Hadoop、Spark、Flink、Hive、HBae、Kafka、ES、Zookeeper等。 开源给大家&#xff0c;若感觉不错欢迎star~ 摘取Flink部分如下文章目录FlinkFli…

Zebra ZT410 ZT411 导入中文字体

1.设置--发送到打印机 2.字体--添加--下载 --windows 字体库下载到zebra 打印机 3.字体--工具--调用 ZEBRA驱动包中驱动。 4.老版打印机导入方式 Zebra ZPL条形码打印机上如何下载和使用TrueType或External字体-敏用数码(上海北京济南洛阳)|专注于条码数据处理 (chongshang.co…

Python空间分析| 01 利用Python计算全局莫兰指数(Global Moran‘s I)

全局空间自相关 空间自相关&#xff08;spatial autocorrelation&#xff09;是指一些变量在同一个分布区内的观测数据之间潜在的相互依赖性。Tobler(1970)曾指出“地理学第一定律&#xff1a;任何东西与别的东西之间都是相关的&#xff0c;但近处的东西比远处的东西相关性更强…

创建自己的脚手架(二)

创建自己的脚手架&#xff08;二&#xff09; 接着之前的功能继续开发 脚手架创建组件功能 pnpm add ejs 修改index.js中的文件内容 增加addcpn功能 #!/usr/bin/env node const { program } require("commander"); const { addComponentAction, createProjectAc…

mybatis 第一章 mybatis简介

1.mybatis历史 MyBatis最初是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation迁移到了Google Code。随着开发团队转投Google Code旗下&#xff0c; iBatis3.x正式更名为MyBatis。代码于2013年11月迁移到Github。 iBatis一词来源于“internet”和…

【智能算法】遗传算法原理及示例

目录&#xff1a;遗传算法一、流程图二、遗传编码三、适应度函数3.1 常见的适应度函数3.1.1 原始适应度函数3.1.2 标准适应度函数四、基本遗传操作4.1 选择操作4.1.1 比例选择4.1.2 轮盘赌选择4.2 交叉操作4.2.1 二进制交叉4.2.2 单点交叉4.2.3 两点交叉4.2.4 多点交叉4.3 变异…

Twitter被封号了?最详细的申诉教程在此

由于Twitter检测系统是十分敏感的&#xff0c;所以在运营的时候很容易莫名就出现“此账号被封禁”或者“此账号被冻结”的情况。出现这种情况大多是因为账号发送了垃圾信息、面临安全风险、发太多广告或者太久没上线被判为机器人这几个原因。被封号后&#xff0c;我们可以通过向…

Android DataStore Proto存储接入流程详解与使用

一、介绍 通过前面的文字&#xff0c;我们已掌握了DataStore 的存储&#xff0c;但是留下一个尾巴&#xff0c;那就是Proto的接入。 Proto是什么&#xff1f; Protobuf&#xff0c;类似于json和xml&#xff0c;是一种序列化结构数据机制&#xff0c;可以用于数据通讯等场景&a…

【taichi】在Window10上从源码编译太极(用于AOT)

准备工作 Visual studio 2022 建议勾选以下几个&#xff1a; 建议取消中文语言包&#xff0c;只选英文的&#xff08;因为中文有几率会出现BUG&#xff09; 说明&#xff1a;C游戏开发是为了UE AOT用的。 vulkan也是UE AOT要用的。 vulkan貌似不需要显示安装&#xff0c;只…

使用docker-compose部署RocketMQ5.0

简介&#xff1a;使用docker-compose部署rocketmq5.0。文中会介绍docker-compose版本以及需要注意的项第一步&#xff1a;进入hub.docker.com搜索rocketmq我们选择第一个&#xff0c;因为第一个是7个月前更新的&#xff0c;&#xff08;我看有很多博客使用的依旧是最下面的那种…

家政服务小程序实战教程14-立即预约功能开发

上一篇我们开发了客服功能&#xff0c;本篇我们开始开发预约功能。 1 创建数据源 功能开发之前先需要考虑数据源的规划问题&#xff0c;顾客预约某项服务时&#xff0c;需要告知上门时间、联系地址、联系电话&#xff0c;如果需要注明的事项&#xff0c;还需要标明备注。 先…

Vue 双向绑定原理

Vue2 双向绑定原理 mvvm 双向绑定&#xff0c;采用数据劫持结合发布者-订阅者模式的方式&#xff0c;通过 Object.defineProperty() 来 劫持各个属性的 setter、getter&#xff0c;在数据变动时发布消息给订阅者&#xff0c;触发相应的监听回调。 几个要点&#xff1a; 1&#…

分享82个HTML电脑主机模板,总有一款适合您

分享82个HTML电脑主机模板&#xff0c;总有一款适合您 82个HTML电脑主机模板下载链接&#xff1a;https://pan.baidu.com/s/13DGOCgvbxSksMPwJzi2z0g?pwdl0mi 提取码&#xff1a;l0mi Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 云虚拟主机运营商网站模板…

C语言返回类型为指针的一些经典题目(下)

续上一篇文章&#xff0c;上一篇文章题目都很经典&#xff0c;这一篇也不例外。一.返回类型为指针经典题目(下)1.代码(第六题)char *GetMemory3(int num) {char *p (char *)malloc(sizeof(char) * num);return p; } void Test3(void) {char *str NULL;str GetMemory3(100…

应用部署初探:6个保障安全的最佳实践

在之前的文章中&#xff0c;我们了解了应用部署的阶段以及常见的部署模式&#xff0c;包括微服务架构的应用应该如何部署等基本内容。本篇文章将介绍如何安全地部署应用程序。 安全是软件开发生命周期&#xff08;SDLC&#xff09;中的关键部分&#xff0c;同时也需要成为 S…

Java反序列化——CommonsCollections中基础知识

一、CC链总结图二、常见类1、Map类 --> TransformedMapMap类是存储键值对的数据结构。 Apache Commons Collections中实现了TransformedMap &#xff0c;该类可以在一个元素被添加/删除/或是被修改时(即key或value&#xff1a;集合中的数据存储形式即是一个索引对应一个值&a…

2022年Q4业绩超预期,被严重低估的Roku迎来反弹

在资本市场上&#xff0c;Roku应该是流媒体领域最具戏剧性的公司。它曾经被突发的疫情推向股价顶峰&#xff0c;但很快又随着疫情结束跌入谷底。 2022年对于Roku来说&#xff0c;仍然是继续探底的一年。随着股价跌回疫情发生前的水平&#xff0c;争论也开始越来越激烈&#xf…

spring-web HandlerMethodArgumentResolver 源码分析

HandlerMethodArgumentResolver 的使用处&#xff0c;解析参数 org.springframework.web.method.support.InvocableHandlerMethod#getMethodArgumentValues HandlerMethodArgumentResolver 在解析参数时使用&#xff0c;以下是使用处 InvocableHandlerMethod#getMethodArgume…

面试题----集合

概述 从上图可以看出&#xff0c;在Java 中除了以 Map 结尾的类之外&#xff0c; 其他类都实现了 Collection 接⼝。 并且&#xff0c;以 Map 结尾的类都实现了 Map 接⼝List,Set,Map List (对付顺序的好帮⼿)&#xff1a; 存储的元素是有序的、可重复的。 Set (注重独⼀⽆⼆…

2.Telegraf简介

Telegraf简介 Telegraf是Influx公司一款基于插件化的开源指标收集工具.主要结合时序性数据库进行使用,用于性能监控.通常Telegraf会每间隔一段时间抓取一批指标数据并将数据发送给时序性数据库或其他自定义的Output. 官方文档 https://docs.influxdata.com/telegraf/v1.24 与…