• 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 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) 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 implements HttpMessageConverter {
    
    	private List supportedMediaTypes = Collections.emptyList();
        
        //**是否支持读取目标参数类型/写入目标方法返回值类型
    	protected abstract boolean supports(Class clazz);
    
        //**读取http body数据,并转换为目标参数类型的对象
    	protected abstract T readInternal(Class 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 {
    
    	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 extends HttpMessageConverter {
    
        //**第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 extends AbstractHttpMessageConverter
    		implements GenericHttpMessageConverter {
        //**GenericHttpMessageConverter未实现的接口:读取http body数据,并转换为目标参数类型的对象
        T read(Type type, @Nullable Class contextClass, HttpInputMessage inputMessage);
        
        //**AbstractHttpMessageConverter的抽象方法:读取http body数据,并转换为目标参数类型的对象
    	protected abstract T readInternal(Class 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 {
    
        //**构造方法设置支持的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数据;
  • 相关阅读:
    Mysql集群及高可用-Mysql高可用MHA9
    释放静电行为监测识别系统
    计算机等级考试—信息安全三级真题七
    壳聚糖/葡聚糖/纳米羟基磷灰石复合水凝胶/鱼明胶-半乳糖壳聚糖水凝胶肝支架的制备
    代数科学计算:LiveMath Maker v3.6.0 cRACK
    Java日期时间的前世今生
    RAW图像处理软件Capture One 23 Enterprise mac中文版功能特点
    IP 地址查询,快速查询自己的 IP 地址
    隐藏底部任务栏图标的方法
    天谋科技 Timecho 完成近亿元人民币天使轮融资,打造工业物联网原生时序数据库
  • 原文地址:https://blog.csdn.net/zhouping118/article/details/127120901