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数据;
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值