Spring参数解析器、消息转换器

news2024/9/23 13:24:42

Spring怎么读取请求体的数据
在Spring框架下我们写接口时,不用从原始的request中解析参数,而是可以直接用java对象接收即可,常见的形式如下:

    @PostMapping(value = "/name")
    @ResponseBody
    public String name(@RequestBody String name) {
        System.out.println(name);
        return "ok";
    }

    @PostMapping(value = "/user")
    @ResponseBody
    public String user(@RequestBody UserModel userModel) {
        System.out.println(userModel);
        return "ok";
    }

其中 @ResponseBody表示返回字符串数据,而不是返回页面。@RequestBody 表示使用RequestResponseBodyMethodProcessor从请求体中解析参数。请求体中的数据格式有多种,如json、xml、text、二进制流。对于同一种请求体格式,我们在写Controller层接口时,参数类型可以有多种。如json、xml、text格式的数据,在Controller都可以用String格式接收。同样的,同一种请求数据格式,在Controller层可以用多种数据类型接收,如json格式的请求数据,既可以用String接收,又可以用实体类接收。
Spring是怎么实现的呢?是通过 HandlerMethodArgumentResolverHttpMessageConverter完成的。

  1. HandlerMethodArgumentResolver判断是否支持Controller接口上的参数转换,不仅要考虑参数类型,也要考虑参数注解。下面两个接口参数类型都一样,但是他们使用的 HandlerMethodArgumentResolver 却不一样,因为参数上的注解不同,导致Spring选用不同的 HandlerMethodArgumentResolver ,因为要从请求的不同位置读取数据。
    @PostMapping(value = "/name1")
    @ResponseBody
    public String name1(@RequestBody String name) {
        System.out.println(name);
        return "ok";
    }

    @PostMapping(value = "/name2")
    @ResponseBody
    public String name2(@RequestParam String name) {
        System.out.println(name);
        return "ok";
    }

HandlerMethodArgumentResolver 的supportsParameter(MethodParameter parameter);方法返回true表示这个 HandlerMethodArgumentResolver支持这种参数解析。上面的两个方法的 HandlerMethodArgumentResolver分别是RequestResponseBodyMethodProcessor和RequestParamMethodArgumentResolver,他们的supportsParameter方法如下

    // RequestResponseBodyMethodProcessor
	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return parameter.hasParameterAnnotation(RequestBody.class);
	}
	// RequestParamMethodArgumentResolver
	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		if (parameter.hasParameterAnnotation(RequestParam.class)) {
			if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
				RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
				return (requestParam != null && StringUtils.hasText(requestParam.name()));
			}
			else {
				return true;
			}
		}
		else {
			if (parameter.hasParameterAnnotation(RequestPart.class)) {
				return false;
			}
			parameter = parameter.nestedIfOptional();
			if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
				return true;
			}
			else if (this.useDefaultResolution) {
				return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
			}
			else {
				return false;
			}
		}
	}

对于一些不带注解的参数,Spring也会默认帮我们解析,如HttpServletRequest ,这也是因为有相应的参数解析器ServletRequestMethodArgumentResolver

    @GetMapping("/name3")
    @ResponseBody
    public String name3(HttpServletRequest request) {
        System.out.println(request.getParameter("name"));
        return "ok";
    }

// ServletRequestMethodArgumentResolver
@Override
	public boolean supportsParameter(MethodParameter parameter) {
		Class<?> paramType = parameter.getParameterType();
		return (WebRequest.class.isAssignableFrom(paramType) ||
				ServletRequest.class.isAssignableFrom(paramType) ||
				MultipartRequest.class.isAssignableFrom(paramType) ||
				HttpSession.class.isAssignableFrom(paramType) ||
				(pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
				(Principal.class.isAssignableFrom(paramType) && !parameter.hasParameterAnnotations()) ||
				InputStream.class.isAssignableFrom(paramType) ||
				Reader.class.isAssignableFrom(paramType) ||
				HttpMethod.class == paramType ||
				Locale.class == paramType ||
				TimeZone.class == paramType ||
				ZoneId.class == paramType);
	}
  1. HttpMessageConverter是完成请求体数据和目标数据类型转化的过程。如果 HttpMessageConverter的canRead(Class<?> clazz, @Nullable MediaType mediaType);方法返回true,表示这个**HttpMessageConverter**支持将mediaType的请求体数据转成Class<?>类型的Controller参数。我们看两个常用的HttpMessageConverter
    StringHttpMessageConverter
	@Override
	// 判断能否将mediaType格式的请求提数据转换成clazz类型的参数
	public boolean canRead(Class<?> clazz, @Nullable MediaType mediaType) {
		return supports(clazz) && canRead(mediaType);
	}
	// StringHttpMessageConverter只能尝试转成String类型的参数
	@Override
	public boolean supports(Class<?> clazz) {
		return String.class == clazz;
	}
	// 判断StringHttpMessageConverter是否支持这种mediaType格式
	protected boolean canRead(@Nullable MediaType mediaType) {
		if (mediaType == null) {
			return true;
		}
		for (MediaType supportedMediaType : getSupportedMediaTypes()) {
			if (supportedMediaType.includes(mediaType)) {
				return true;
			}
		}
		return false;
	}
	// StringHttpMessageConverter默认支持所有格式
	public StringHttpMessageConverter(Charset defaultCharset) {
		super(defaultCharset, MediaType.TEXT_PLAIN, MediaType.ALL);
	}

注意,再SpringMVC中默认添加的StringHttpMessageConverter字符集是ISO_8859_1,中文参数会乱码。需要自己注册一个,SpringBoot自动注册的就是UTF-8格式的,所以可以正确处理中文。

    // SpringMVC注册的ISO_8859_1字符集的StringHttpMessageConverter 
    public RequestMappingHandlerAdapter() {
		this.messageConverters = new ArrayList<>(4);
		this.messageConverters.add(new ByteArrayHttpMessageConverter());
		// 使用ISO_8859_1字符集
		this.messageConverters.add(new StringHttpMessageConverter());
		if (!shouldIgnoreXml) {
			try {
				this.messageConverters.add(new SourceHttpMessageConverter<>());
			}
			catch (Error err) {
				// Ignore when no TransformerFactory implementation is available
			}
		}
		this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
	}
	// SpringMVC项目中手动注册UTF_8字符集的StringHttpMessageConverter 
    @Bean
    public StringHttpMessageConverter stringHttpMessageConverter() {
        return new StringHttpMessageConverter(StandardCharsets.UTF_8);
    }


	// SpringBoot自动注册UTF_8字符集的StringHttpMessageConverter
	// org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration.StringHttpMessageConverterConfiguration
	// 如果没有在配置文件中配置server.servlet.encoding,则默认就是UTF_8字符集
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(StringHttpMessageConverter.class)
	protected static class StringHttpMessageConverterConfiguration {

		@Bean
		@ConditionalOnMissingBean
		public StringHttpMessageConverter stringHttpMessageConverter(Environment environment) {
			Encoding encoding = Binder.get(environment).bindOrCreate("server.servlet.encoding", Encoding.class);
			StringHttpMessageConverter converter = new StringHttpMessageConverter(encoding.getCharset());
			converter.setWriteAcceptCharset(false);
			return converter;
		}
	}

MappingJackson2HttpMessageConverter

	@Override
	public boolean canRead(Class<?> clazz, @Nullable MediaType mediaType) {
		return canRead(clazz, null, mediaType);
	}

	@Override
	public boolean canRead(Type type, @Nullable Class<?> contextClass, @Nullable MediaType mediaType) {
		if (!canRead(mediaType)) {
			return false;
		}
		JavaType javaType = getJavaType(type, contextClass);
		// 根据参数类型和mediaType选择合适的ObjectMapper 
		ObjectMapper objectMapper = selectObjectMapper(javaType.getRawClass(), mediaType);
		if (objectMapper == null) {
			return false;
		}
		AtomicReference<Throwable> causeRef = new AtomicReference<>();
		// 判断选出的ObjectMapper 是否可以反序列化目标参数类型
		if (objectMapper.canDeserialize(javaType, causeRef)) {
			return true;
		}
		logWarningIfNecessary(javaType, causeRef.get());
		return false;
	}

现在我们知道Spring是怎么默默完成从请求体数据向接口参数类型的转化了:

  1. 根据参数类型和注解确认使用哪个HandlerMethodArgumentResolver
  2. 判断这个HandlerMethodArgumentResolver里是否有合适的HttpMessageConverter
  3. 一般用于接收请求体数据的参数类型是String和实体类(包括集合数据),前者是用StringHttpMessageConverter,后者使用MappingJackson2HttpMessageConverter。StringHttpMessageConverter是直接读取请求体的inputStream,再转成String。MappingJackson2HttpMessageConverter是借助ObjectMapper 的各种JsonDeserializer完成数据转化(即反序列化),可以看到JsonDeserializer的实现类很多,常见的反序列化场景都能满足。但是对枚举类的反序列化支持不够友好,只能根据枚举的ordinal值进行反序列化,而我们一般会自定义code值,我们更希望根据自定义code值反序列化成对应的枚举对象。

了解以上背景后我们可以做两个扩展

  1. 自定义参数解析器,根据自定义的@User注解,从请求头中解析出用户信息
  2. 自定义反序列化器,支持数字到枚举的反序列化

可以看到,Spring参数解析器、消息转换器是从两个维度定义参数解析。参数解析器确定从哪里读取数据,消息转换器是真正执行消息转换的工作。我们也可以根据需要,从这两个维度进行自定义。

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

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

相关文章

C++之GradeBook类

主文件 #include <iostream> #include "GradeBook.h"using namespace std;int main() {GradeBook myGradeBook; // 创建一个对象cout << "请输入课程名称:" ;string courseName;cin >> courseName;cout << "请输入学生人数&…

iOS常见锁及应用(笔记版)

什么是锁&#xff1f; 在程序中&#xff0c;当多个任务&#xff08;或线程&#xff09;同时访问同一个资源时&#xff0c;比如多个操作同时修改一份数据&#xff0c;可能会导致数据不一致。这时候&#xff0c;我们需要“锁”来确保同一时间只有一个任务能够操作这个数据&#…

vue打包exe之electron-quick-start的npm install 报错

vue打包exe之electron-quick-start的npm install 报错 1、github地址2、问题3、解决4、其他(打包exe)参考 1、github地址 https://github.com/electron/electron-quick-start2、问题 我使用的pnpm install正常安装&#xff0c;执行npm start提示错误 3、解决 在package.js…

Python之一些列表的练习题

1.比较和对比字符串、列表和元组。例如&#xff0c;它们可以容纳哪类内容以及在数据结构上可以做哪些操作。 1. 内容类型:- 字符串: 只能包含字符(文本)。- 列表: 可以包含任意类型的数据,如数字、字符串、其他列表等。- 元组: 可以包含任意类型的数据,与列表类似。3. 操作:(1…

Kaggle-狗种类的识别(Pytorch框架)基本图像识别流程

狗类别实现过程 一. 将数据集按标签分类&#xff0c;将标签转换为数字表示&#xff0c;并制作数据集 二. 搭建网络框架&#xff0c;inception&#xff0c;或者ResNet 三. 选择优化函数&#xff0c;训练模型 数据集制作 首先分析数据集&#xff0c;题中已经很明确告诉有120 种…

头晕,脖子酸痛?颈椎有问题,人就废了一半!颈椎病分3级,不同阶段治疗方法不一样!

每天下午快下班时&#xff0c;在办公室就会看到一种现象&#xff1a; 大家纷纷扭脖子、抬头、耸肩膀......诶&#xff0c;脖子太难受了&#xff01; 毕竟每天长时间的面对电脑&#xff0c;我们的脖子在承受着巨大的压力。尤其&#xff0c;低头 45 度时&#xff0c;脖子甚至承受…

Fyne ( go跨平台GUI )中文文档- 扩展Fyne (七)

本文档注意参考官网(developer.fyne.io/) 编写, 只保留基本用法 go代码展示为Go 1.16 及更高版本, ide为goland2021.2 这是一个系列文章&#xff1a; Fyne ( go跨平台GUI )中文文档-入门(一)-CSDN博客 Fyne ( go跨平台GUI )中文文档-Fyne总览(二)-CSDN博客 Fyne ( go跨平台GUI…

图像处理软件,常用于照片编辑和修饰

一、简介 1、一款功能强大的图像处理软件&#xff0c;常用于照片编辑和修饰。它提供多种工具和特效&#xff0c;允许用户调整照片的亮度、对比度、色彩、锐化等 二、下载 1、文末有下载链接,不明白可以私聊我哈&#xff08;麻烦咚咚咚&#xff0c;动动小手给个关注收藏小三连&a…

【掘金量化使用技巧】用日线合成长周期k线

掘金API中的接口最长的周期是‘1d’的&#xff0c;因此周线/月线/年线等数据需要自己进行合成。 基本思路 用日线合成长周期的k线只需要确定好合成的周期以及需要的数据即可。 周期: 一般行情软件上提供年k、月k、周k&#xff0c;我也选择年、月、周再加一个季度频率。 数据:…

图纸加密防泄密软件 | 从设计到交付,2024年值得关注的图纸加密软件大盘点!

图纸者&#xff0c;匠心之凝聚&#xff0c;智慧之结晶。然&#xff0c;信息之海浩瀚无垠&#xff0c;暗流涌动&#xff0c;图纸之安全&#xff0c;实乃企业之头等大事。 故&#xff0c;择一良器&#xff0c;以密护图纸&#xff0c;实为当务之急。 以下&#xff0c;七款图纸加密…

Linux之实战命令01:xargs应用实例(三十五)

简介&#xff1a; CSDN博客专家、《Android系统多媒体进阶实战》一书作者 新书发布&#xff1a;《Android系统多媒体进阶实战》&#x1f680; 优质专栏&#xff1a; Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a; 多媒体系统工程师系列【…

高刷显示器哪个好?540Hz才有资格称高刷

高刷显示器哪个好&#xff1f;说实话&#xff0c;540Hz这些才能成为高刷显示器&#xff0c;什么200,240的&#xff0c;都不够高&#xff0c;什么是从容&#xff0c;有我不用才叫从容。下面我们一起来看看540Hz的高刷显示器都有哪些吧&#xff01; 1.高刷显示器哪个好 - 蚂蚁电…

2024风湿免疫科常用评估量表汇总,附操作步骤与评定标准!

常笑医学整理了5个风湿免疫科常用的评估量表&#xff0c;包括类风湿关节炎患者病情评价&#xff08;DAS28&#xff09;、系统性狼疮活动性测定&#xff08;SLAM&#xff09;等。这些量表在常笑医学网均支持在线评估、下载和创建项目使用。 01 类风湿关节炎患者病情评价 &#x…

实践中如何选择o1或sonnet3-5?

简述 AI更新太快导致我们不知选择什么使用更好&#xff1f;本文对比了新模型o1系列和Claude-3.5-sonnet的一些特点&#xff0c;针对不同开发场景提供了选择建议&#xff0c;希望能为你提供一些模型选择的参考。 模型对比 o1系列: 优势&#xff1a; 推理能力非常强&#xff0…

【动态规划】两个数组的 dp 问题二

两个数组的 dp 问题 1.正则表达式匹配2.交错字符串3.两个字符串的最小ASCII删除和4.最长重复子数组 点赞&#x1f44d;&#x1f44d;收藏&#x1f31f;&#x1f31f;关注&#x1f496;&#x1f496; 你的支持是对我最大的鼓励&#xff0c;我们一起努力吧!&#x1f603;&#x1…

高德地图自定义点标记

const markerContent <div class"custom-content-marker"> <span>摄像机<span> <img src"//a.amap.com/jsapi_demos/static/demo-center/icons/poi-marker-red.png"> </div> marker.value new AMap.Marker({position:…

Ubuntu搭建java开发环境

一&#xff1a;Ubuntu安装 1、下载Ubuntu 24.04.1 LTS 官网下载地址&#xff1a;https://releases.ubuntu.com/24.04.1/ubuntu-24.04.1-desktop-amd64.iso 可以直接点击这里下载 2、使用VMware安装 新建虚拟机 之后一直下一步&#xff0c;到如下界面&#xff0c;选择 刚刚…

【MYSQL】聚合查询、分组查询、联合查询

目录 聚合查询聚合函数count()sum()avg()max()和min()总结 分组查询group by 子句having 子句 联合查询笛卡尔积内连接外连接自连接子查询单行子查询多行子查询from子句使用子查询 合并查询 聚合查询 聚合查询就是针对表中行与行之间的查询。 聚合函数 count() count(列名)&a…

战神5/战神:诸神黄昏/God of War Ragnarok(容量175GB)百度网盘下载

版本介绍 v1.0.612.4312|容量175GB|官方简体中文|支持键盘.鼠标.手柄|赠单板学习补丁 配置要求 战神5/战神&#xff1a;诸神黄昏/God of War Ragnarok 游戏介绍 不灭的北欧传奇 由Santa Monica Studio出品、Jetpack Interactive负责PC移植的佳作《God of War Ragnark》将带您…

python实现语音唤醒

1. 环境 python版本&#xff1a;3.11.9 2.完整代码 import sqlite3 import timefrom funasr import AutoModel import sounddevice as sd import numpy as np from pypinyin import lazy_pinyin# 模型参数设置 chunk_size [0, 10, 5] encoder_chunk_look_back 7 decoder_c…