当前位置: 首页 > news >正文

springboot-方法处理4-消息转换器

1.消息转换器简介

在《方法处理2-参数解析器》那一章,了解到获取body参数的解析器AbstractMessageConverterMethodArgumentResolver及其子类,并没有自己去解析request中的body参数,而是委托消息转换器HttpMessageConverter去解析body参数,并转换为目标方法的参数类型,本文稍微研究一下消息转换器;

1.1.类结构

消息转换器类结构

1.2.功能说明(重点)

消息转换器负责读取httpRequest请求中的body参数,并转换为目标方法的参数类型。并把目标方法的返回值写入到httpResponse中;

支持的数据格式支持的参数类型名称支持的header类型
字节Byte[]ByteArrayHttpMessageConverter 1.application/octet-stream类型读取和写入 2.*/*类型读取和写入
文件ResourceResourceHttpMessageConverter 1.*/*类型读取和写入
文本StringStringHttpMessageConverter 1.text/plain类型读取和写入 2.*/*类型读取和写入
任意类型 ObjectToStringHttpMessageConverter 先读取文本,然后使用ConversionService把文本转换为对象 1.text/plain类型读取和写入
MultiValueMap FormHttpMessageConverter 使用其它的消息转换器,把MultiValueMap返回值中包含的对象转换成字节/文本并写入响应 1.application/x-www-form-urlencoded类型读取和写入 2.multipart/form-data类型写入 3.multipart/mixed类型写入 4.*/*类型写入
MultiValueMap AllEncompassingFormHttpMessageConverter 使用其它的消息转换器,把MultiValueMap返回值中包含的对象转换成json/xml并写入响应
xmlSource SourceHttpMessageConverter 使用jdk的api,把xml转换为Source对象 1.application/xml类型读取和写入 2.text/xml类型读取和写入 3.application/*+xml类型读取和写入
任意类型,类包含如下注解: 1.读取:@XmlType/@XmlRootElement 2.写入:@XmlRootElement Jaxb2RootElementHttpMessageConverter 使用jakarta.xml,把xml转换为对象
任意类型 MappingJackson2XmlHttpMessageConverter 使用jackson.xml,把json转换为对象
json任意类型 FastJsonHttpMessageConverter 使用fastjson,把json转换为对象 1.*/*类型读取和写入
任意类型 JsonbHttpMessageConverter 使用javax.json,把json转换为对象 1.application/json类型读取和写入 2.application/*+json类型读取和写入
任意类型 GsonHttpMessageConverter 使用google.gson,把json转换为对象
任意类型 MappingJackson2HttpMessageConverter 使用jackson.json,把json转换为对象

2.消息转换器源码

消息转换器提供了两个接口HttpMessageConverter和GenericHttpMessageConverter,GenericHttpMessageConverter支持推断泛型类型。我们根据这两个接口把消息转换器分成两类:第一类是HttpMessageConverter及其子类,第二类为GenericHttpMessageConverter及其子类;

2.1.HttpMessageConverter及其子类

2.1.1.HttpMessageConverter

HttpMessageConverter是参数转换器的顶层接口,定义了参数转换器的行为,包括6个方法。分成三种类型,每种类型2个方法;

  • 读取httpRequest请求中的body参数,并转换为目标方法的参数
  • 把目标方法的返回值转换成指定类型,并写入到httpResponse
  • 获取支持的http contentType类型;
public interface HttpMessageConverter<T> {
    //**第1类: 读取参数
    //**是否支持读取目标参数类型和http contentType
	boolean canRead(Class<?> clazz, MediaType mediaType);
	//**读取http body数据,并转换为目标参数类型的对象
	T read(Class<? extends T> clazz, HttpInputMessage inputMessage);
	
	//**第2类: 写入参数
    //**是否支持写入目标方法的返回值类型和http contentType
	boolean canWrite(Class<?> clazz, MediaType mediaType);
	//**把目标方法的返回值写入http response
	void write(T t, MediaType contentType, HttpOutputMessage outputMessage);
	
	//**第3类: 获取支持的contentType
    //**获取支持的http contentType类型
	List<MediaType> getSupportedMediaTypes();
    //**根据目标参数类型获取支持的http contentType类型
	default List<MediaType> getSupportedMediaTypes(Class<?> clazz) {
		return (canRead(clazz, null) || canWrite(clazz, null) ?
				getSupportedMediaTypes() : Collections.emptyList());
	}
}

2.1.2.FormHttpMessageConverter

FormHttpMessageConverter是HttpMessageConverter的直接子类,接收参数/返回值必须是MultiValueMap;

读取

FormHttpMessageConverter支持application/x-www-form-urlencoded类型的读取(FormHttpMessageConverter及其子类AllEncompassingFormHttpMessageConverter是唯二可以读取x-www-form-urlencoded参数的实现类);

读取过程很简单(参数name=test1&sex=38name=test2),使用&和=切割,最后保存到MultiValueMap中,参数名和参数值都是字符串。同参数名可以有多个,所以值是数组;

写入

FormHttpMessageConverter支持application/x-www-form-urlencodeda || multipart/form-data || multipart/mixed || */*类型的写入;

  • 如果内容不是multipart(contentType包含multipart || contentType为null但是MultiValueMap返回值中的value有非字符串类型则认为内容是multipart),直接写入到response中;
  • 如果内容是multipart,则根据value的类型分别调用其它的消息转换器,把value转换后写入响应,转换后的类型为byte[]/String/Resource。可以添加新的消息转换器支持新的转换类型(比如其子类AllEncompassingFormHttpMessageConverter,加入了json和xml消息转换器,可以把value转换为json和xml);

源码

构造方法添加默认支持的contentType和用于转换multipart的消息转换器;

public FormHttpMessageConverter() {
    //**添加支持的contentType
	this.supportedMediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED);
	this.supportedMediaTypes.add(MediaType.MULTIPART_FORM_DATA);
	this.supportedMediaTypes.add(MediaType.MULTIPART_MIXED);
    
    //**添加消息转换器,用于把MultiValueMap返回值中包含的对象转换成字节/文本并写入响应
	this.partConverters.add(new ByteArrayHttpMessageConverter());
	this.partConverters.add(new StringHttpMessageConverter());
	this.partConverters.add(new ResourceHttpMessageConverter());
}

判断是否支持写入目标方法的返回值类型和http contentType;

@Override
	public boolean canRead(Class<?> clazz, @Nullable MediaType mediaType) {
	    //**目标参数必须是MultiValueMap
		if (!MultiValueMap.class.isAssignableFrom(clazz)) {
			return false;
		}
		if (mediaType == null) {
			return true;
		}
		for (MediaType supportedMediaType : getSupportedMediaTypes()) {
		    //**不支持包含multipart的contentType
			if (supportedMediaType.getType().equalsIgnoreCase("multipart")) {
				continue;
			}
			if (supportedMediaType.includes(mediaType)) {
				return true;
			}
		}
		return false;
	}

判断是是否multipart内容;

//**contentType包含multipart || contentType为null但是MultiValueMap返回值中的value有非字符串类型则认为内容是multipart
private boolean isMultipart(MultiValueMap<String, ?> map, @Nullable MediaType contentType) {
	if (contentType != null) {
		return contentType.getType().equalsIgnoreCase("multipart");
	}
	for (List<?> values : map.values()) {
		for (Object value : values) {
			if (value != null && !(value instanceof String)) {
				return true;
			}
		}
	}
	return false;
}

如果是multipart内容,调用其它的消息转换器,把value转换后写入响应;

private void writePart(String name, HttpEntity<?> partEntity, OutputStream os) throws IOException {
	Object partBody = partEntity.getBody();
	if (partBody == null) {
		throw new IllegalStateException("Empty body for part '" + name + "': " + partEntity);
	}
	Class<?> partType = partBody.getClass();
	HttpHeaders partHeaders = partEntity.getHeaders();
	MediaType partContentType = partHeaders.getContentType();
	for (HttpMessageConverter<?> messageConverter : this.partConverters) {
		if (messageConverter.canWrite(partType, partContentType)) {
			Charset charset = isFilenameCharsetSet() ? StandardCharsets.US_ASCII : this.charset;
			HttpOutputMessage multipartMessage = new MultipartHttpOutputMessage(os, charset);
			multipartMessage.getHeaders().setContentDispositionFormData(name, getFilename(partBody));
			if (!partHeaders.isEmpty()) {
				multipartMessage.getHeaders().putAll(partHeaders);
			}
			((HttpMessageConverter<Object>) messageConverter).write(partBody, partContentType, multipartMessage);
			return;
		}
	}
	throw new HttpMessageNotWritableException("Could not write request: no suitable HttpMessageConverter " +
			"found for request type [" + partType.getName() + "]");
}

2.1.3.AbstractHttpMessageConverter

  • 抽象类,实现了HttpMessageConverter的接口,主要功能是处理了添加了contentType,开放出来的抽象方法中没有contentType参数;
  • 添加List supportedMediaTypes保存支持的contentType。子类支持的contentType只需要添加到此list,读取和写入的相关方法不需要关注contentType;
public abstract class AbstractHttpMessageConverter<T> implements HttpMessageConverter<T> {

	private List<MediaType> supportedMediaTypes = Collections.emptyList();
    
    //**是否支持读取目标参数类型/写入目标方法返回值类型
	protected abstract boolean supports(Class<?> clazz);

    //**读取http body数据,并转换为目标参数类型的对象
	protected abstract T readInternal(Class<? extends T> clazz, HttpInputMessage inputMessage)
			throws IOException, HttpMessageNotReadableException;

    //**把目标方法的返回值写入http body
	protected abstract void writeInternal(T t, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException;

}

2.1.4.ObjectToStringHttpMessageConverter

  • ObjectToStringHttpMessageConverter是AbstractHttpMessageConverter的子类,支持text/plain类型读取和写入,参数/返回值类型可以是任意对象(非文件);
  • 读取:先使用StringHttpMessageConverter把参数转换为String,然后调用ConversionService(转换服务是一个新的接口)把String转换为参数对象;
  • 写入:先使用ConversionService把返回值对象转换为String,然后使用StringHttpMessageConverter写入response;
public class ObjectToStringHttpMessageConverter extends AbstractHttpMessageConverter<Object> {

	private final ConversionService conversionService;
	private final StringHttpMessageConverter stringHttpMessageConverter;

    //**先使用StringHttpMessageConverter把参数转换为String,然后调用ConversionService把String转换为参数对象
	@Override
	protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage)
			throws IOException, HttpMessageNotReadableException {
		String value = this.stringHttpMessageConverter.readInternal(String.class, inputMessage);
		Object result = this.conversionService.convert(value, clazz);
		...
		return result;
	}

    //**先使用ConversionService把返回值对象转换为String,然后使用StringHttpMessageConverter写入response
	@Override
	protected void writeInternal(Object obj, HttpOutputMessage outputMessage) throws IOException {
		String value = this.conversionService.convert(obj, String.class);
		if (value != null) {
			this.stringHttpMessageConverter.writeInternal(value, outputMessage);
		}
	}
}

2.2.GenericHttpMessageConverter及其子类

2.2.1.GenericHttpMessageConverter

  • HttpMessageConverter的子接口,支持目标参数/返回值为泛型,可获取泛型的具体类型;
  • GenericHttpMessageConverter读取和写入的接口都比HttpMessageConverter多了一个Type type参数。type是目标参数/返回值属方法所在的类的Type对象,用于当目标参数/返回值是泛型类型时,推断泛型的具体类型。例如:public Object test(@RequestBody T test),HttpMessageConverter接口无法获取参数test的类型;
public interface GenericHttpMessageConverter<T> extends HttpMessageConverter<T> {

    //**第1类: 读取参数
    //**是否支持读取目标参数类型和http contentType
	boolean canRead(Type type, @Nullable Class<?> contextClass, @Nullable MediaType mediaType);
    //**读取http body数据,并转换为目标参数类型的对象
	T read(Type type, @Nullable Class<?> contextClass, HttpInputMessage inputMessage);

    //**第2类: 写入参数
    //**是否支持写入目标方法的返回值类型和http contentType
	boolean canWrite(@Nullable Type type, Class<?> clazz, @Nullable MediaType mediaType);
	//**把目标方法的返回值写入http response
	void write(T t, @Nullable Type type, @Nullable MediaType contentType, HttpOutputMessage outputMessage);

}

2.2.2.AbstractGenericHttpMessageConverter

  • 上面GenericHttpMessageConverter接口继承了HttpMessageConverter接口,那GenericHttpMessageConverter接口就存在了很多接口;
  • AbstractGenericHttpMessageConverter实现GenericHttpMessageConverter接口的同时,继承AbstractHttpMessageConverter,实现了处理contentType的能力。开放出来的抽象方法中同样没有contentType参数;
public abstract class AbstractGenericHttpMessageConverter<T> extends AbstractHttpMessageConverter<T>
		implements GenericHttpMessageConverter<T> {
    //**GenericHttpMessageConverter未实现的接口:读取http body数据,并转换为目标参数类型的对象
    T read(Type type, @Nullable Class<?> contextClass, HttpInputMessage inputMessage);
    
    //**AbstractHttpMessageConverter的抽象方法:读取http body数据,并转换为目标参数类型的对象
	protected abstract T readInternal(Class<? extends T> clazz, HttpInputMessage inputMessage)
			throws IOException, HttpMessageNotReadableException;
    
    //**自己的抽象方法:把目标方法的返回值写入http response
	protected abstract void writeInternal(T t, @Nullable Type type, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException;

}

2.2.3.AbstractJsonHttpMessageConverter

  • 继承AbstractGenericHttpMessageConverter,接收json数据转换为目标参数/把目标方法返回值转换为json数据,写入到response;
  • 支持的contentType包含application/json和application/*+json;
  • 其子类JsonbHttpMessageConverter和GsonHttpMessageConverter都是使用第三方的json包,把json数据转换为对象/把对象转换为json数据(自定json格式的读取和写入也最好继承些抽象类);
public abstract class AbstractJsonHttpMessageConverter extends AbstractGenericHttpMessageConverter<Object> {

    //**构造方法设置支持的contentType
	public AbstractJsonHttpMessageConverter() {
		super(MediaType.APPLICATION_JSON, new MediaType("application", "*+json"));
		setDefaultCharset(DEFAULT_CHARSET);
	}

    //**写入到response时,支持在json数据加入前缀,防止被劫持
	@Override
	protected final void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage) {
		Writer writer = getWriter(outputMessage);
		if (this.jsonPrefix != null) {
			writer.append(this.jsonPrefix);
		}
		...
	}

    //**读取http body数据,并转换为目标参数类型的对象
	protected abstract Object readInternal(Type resolvedType, Reader reader) throws Exception;

    //**把目标方法的返回值写入http response
	protected abstract void writeInternal(Object object, @Nullable Type type, Writer writer) throws Exception;
}

2.2.4.AbstractJackson2HttpMessageConverter

  • 继承AbstractGenericHttpMessageConverter,spring专门为第三方包jackson定义了此抽象方法;
  • 定义了jackson包进行数据转换的一些配置和依赖,方便子类使用jackson的相关api进数据读取和转换;
  • 其子类MappingJackson2HttpMessageConverter和MappingJackson2XmlHttpMessageConverter分别支持json数据和xml数据;

相关文章:

  • FPGA底层资源综述
  • CLIP扩展
  • 从一维卷积、因果卷积(Causal CNN)、扩展卷积(Dilation CNN) 到 时间卷积网络 (TCN)
  • 高等数学(第七版)同济大学 习题8-2 个人解答
  • [HJ56 完全数计算]
  • 【nlp】天池学习赛-新闻文本分类-机器学习
  • 机器人系统,如何快速算法开发与原型机验证?
  • 调用静态方法
  • Vue的生命周期详解
  • 机器人控制算法九之机器人建模(XML)、工作场景Scances建模(VRML)
  • 【Unity3D日常开发】Unity3D中打包WEBGL后读取本地文件数据
  • 【SDS V6 专题】开放内容平台,XOCP 助力数据常青
  • 鲜花绿植学生网页设计模板 静态HTML鲜花学生网页作业成品 DIV CSS网上鲜花植物主题静态网页
  • 国庆在家没事干?教大家用Python做一个任何视频都能看的软件, 当然,只能看正经的
  • NumPy数据分析基础:NumPy特性以及Python内置数据结构对比详解
  • 9月CHINA-PUB-OPENDAY技术沙龙——IPHONE
  • docker容器内的网络抓包
  • Java IO学习笔记一
  • LeetCode18.四数之和 JavaScript
  • Less 日常用法
  • Magento 1.x 中文订单打印乱码
  • Mysql数据库的条件查询语句
  • SAP云平台里Global Account和Sub Account的关系
  • 回顾 Swift 多平台移植进度 #2
  • 京东美团研发面经
  • 如何优雅的使用vue+Dcloud(Hbuild)开发混合app
  • 由插件封装引出的一丢丢思考
  • 在electron中实现跨域请求,无需更改服务器端设置
  • 找一份好的前端工作,起点很重要
  • 大数据全解:定义、价值及挑战
  • ​猴子吃桃问题:每天都吃了前一天剩下的一半多一个。
  • (13)Latex:基于ΤΕΧ的自动排版系统——写论文必备
  • (AtCoder Beginner Contest 340) -- F - S = 1 -- 题解
  • (C语言)输入自定义个数的整数,打印出最大值和最小值
  • (附源码)ssm失物招领系统 毕业设计 182317
  • (附源码)小程序 交通违法举报系统 毕业设计 242045
  • (免费领源码)Java#ssm#MySQL 创意商城03663-计算机毕业设计项目选题推荐
  • (五)关系数据库标准语言SQL
  • (转载)深入super,看Python如何解决钻石继承难题
  • *上位机的定义
  • .bat批处理(九):替换带有等号=的字符串的子串
  • .net core 客户端缓存、服务器端响应缓存、服务器内存缓存
  • .NET Core 实现 Redis 批量查询指定格式的Key
  • .Net 中Partitioner static与dynamic的性能对比
  • .NET 中的轻量级线程安全
  • .NET开发不可不知、不可不用的辅助类(三)(报表导出---终结版)
  • .Net下C#针对Excel开发控件汇总(ClosedXML,EPPlus,NPOI)
  • .vollhavhelp-V-XXXXXXXX勒索病毒的最新威胁:如何恢复您的数据?
  • /usr/bin/perl:bad interpreter:No such file or directory 的解决办法
  • @RequestMapping 的作用是什么?
  • [ C++ ] STL_vector -- 迭代器失效问题
  • [ vulhub漏洞复现篇 ] Django SQL注入漏洞复现 CVE-2021-35042
  • [.NET 即时通信SignalR] 认识SignalR (一)
  • [1204 寻找子串位置] 解题报告
  • [ARC066F]Contest with Drinks Hard