springMVC消息转换器HttpMessageConverter

前言

  • 为何需要消息转换器

HttpMessageConverter是用来处理request和response里的数据的。

请求和响应都有对应的body,而这个body就是需要关注的主要数据。
请求体的表述一般就是一段字符串,当然也可以是二进制数据(比如上传~)。
响应体则是浏览器渲染页面的依据,对于一个普通html页面得响应,响应体就是这个html页面的源代码。

请求体和响应体都是需要配合Content-Type头部使用的,这个头部主要用于说明body中得字符串是什么格式的,比如:text,json,xml等。对于请求报文,只有通过此头部,服务器才能知道怎么解析请求体中的字符串,对于响应报文,浏览器通过此头部才知道应该怎么渲染响应结果,是直接打印字符串还是根据代码渲染为一个网页

对于HttpServletRequest和HttpServletResponse,可以分别调用getInputStream和getOutputStream来直接获取body。但是获取到的仅仅只是一段字符串而对于java来说,处理一个对象肯定比处理一个字符串要方便得多,也好理解得多所以根据Content-Type头部,将body字符串转换为java对象是常有的事。反过来,根据Accept头部,将java对象转换客户端期望格式的字符串也是必不可少的工作。这就是我们本文所讲述的消息转换器的工作~

消息转换器它能屏蔽你对底层转换的实现,分离你的关注点,让你专心操作java对象,其余的事情你就交给我Spring MVC吧~大大提高你的编码效率(可议说比源生Servlet开发高级太多了)

一、消息转换器的架构

主要部分类图如下
在这里插入图片描述

spring消息转换器使用了策略模式

1、策略接口

HttpMessageConverter接口是Spring3.0之后新增的一个接口,它负责将请求信息转换为一个对象(类型为T),并将对象(类型为T)绑定到请求方法的参数中或输出为响应信息

public interface HttpMessageConverter<T> {
	//接收到请求,判断是否能读
	boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
	
	//能读则读,读的逻辑
	T read(Class<? extends T> clazz, HttpInputMessage inputMessage)throws ...;

	//返回结果时判断是否能写
	boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
   
	//能写则写,写的逻辑
	void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage) throws ...;
	
	 //获取支持的 MediaType
	List<MediaType> getSupportedMediaTypes();

HttpMessageConverter是不支持参数类型为泛型的读写功能的,在不修改接口的情况下。为了增加其功能,所以spring又为消息转换器增加了另一策略接口GenericHttpMessageConverter

GenericHttpMessageConverter接口继承自HttpMessageConverter接口,他是对AbstractHttpMessageConverter类对补充。增加处理参数类型为泛型的功能

public interface GenericHttpMessageConverter<T> extends HttpMessageConverter<T> {
	// 它的效果同父接口的canRead,但是它是加了一个泛型类型~~~来加以更加详细的判断
	boolean canRead(Type type, @Nullable Class<?> contextClass, @Nullable MediaType mediaType);
	// 一样也是加了泛型类型
	T read(Type type, @Nullable Class<?> contextClass, HttpInputMessage inputMessage) throws .
	
	boolean canWrite(@Nullable Type type, Class<?> clazz, @Nullable MediaType mediaType);
	
	void write(T t, @Nullable Type type, @Nullable MediaType contentType, HttpOutputMessage outputMessage) throws .
2、模版类
public abstract class AbstractHttpMessageConverter<T> implements HttpMessageConverter<T> {
    // 缓存下它所支持的MediaType们
	private List<MediaType> supportedMediaTypes = Collections.emptyList();

	@Override
	public boolean canRead(Class<?> clazz, @Nullable MediaType mediaType) {
	    //supports方法留给子类实现了
	    //canRead方法内容是如果mediaType为null或者此转发器支持此mediaType,返回true
		return supports(clazz) && canRead(mediaType);
	}
	
	@Override
	public boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType) {
	    //supports方法留给子类实现了
	    //canWrite方法内容是如果mediaType为null或者此转发器支持此mediaType,返回true
		return supports(clazz) && canWrite(mediaType);
	}
	@Override
	public final T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
			throws IOException, HttpMessageNotReadableException {
        //readInternal方法给子类实现
		return readInternal(clazz, inputMessage);
	}
	@Override
	public final void write(final T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException {

		final HttpHeaders headers = outputMessage.getHeaders();
		addDefaultHeaders(headers, t, contentType);

		if (outputMessage instanceof StreamingHttpOutputMessage) {
			StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;
			streamingOutputMessage.setBody(outputStream -> writeInternal(t, new HttpOutputMessage() {
				@Override
				public OutputStream getBody() {
					return outputStream;
				}
				@Override
				public HttpHeaders getHeaders() {
					return headers;
				}
			}));
		}
		else {
		    //writeInternal子类实现
			writeInternal(t, outputMessage);
			// 最后它执行了flush,这也就是为何我们自己一般不需要flush的原因
			outputMessage.getBody().flush();
		}
	}

外部消息转换器绝大数都是实现这个类, 子类要实现以下方法

boolean supports(Class<?> clazz)  //支持此转换器的条件
readInternal(Class<? extends T> clazz, HttpInputMessage inputMessage)  //写的逻辑
writeInternal(T t, HttpOutputMessage outputMessage) //读的逻辑

注意,这个抽象类是没有处理参数类型为泛型的功能。要想要子类具备处理参数类型为泛型的功能。需要实现另一个抽象类
AbstractGenericHttpMessageConverter,他是AbstractHttpMessageConverter和

public abstract class AbstractGenericHttpMessageConverter<T> extends AbstractHttpMessageConverter<T>
		implements GenericHttpMessageConverter<T> {


	@Override
	protected boolean supports(Class<?> clazz) {
		return true;
	}

	@Override
	public boolean canRead(Type type, @Nullable Class<?> contextClass, @Nullable MediaType mediaType) {
		return (type instanceof Class ? canRead((Class<?>) type, mediaType) : canRead(mediaType));
	}

	@Override
	public boolean canWrite(@Nullable Type type, Class<?> clazz, @Nullable MediaType mediaType) {
		return canWrite(clazz, mediaType);
	}


	@Override
	public final void write(final T t, @Nullable final Type type, @Nullable MediaType contentType,
			HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {

		final HttpHeaders headers = outputMessage.getHeaders();
		addDefaultHeaders(headers, t, contentType);

		if (outputMessage instanceof StreamingHttpOutputMessage) {
			StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;
			streamingOutputMessage.setBody(outputStream -> writeInternal(t, type, new HttpOutputMessage() {
				@Override
				public OutputStream getBody() {
					return outputStream;
				}
				@Override
				public HttpHeaders getHeaders() {
					return headers;
				}
			}));
		}
		else {
			writeInternal(t, type, outputMessage);
			outputMessage.getBody().flush();
		}
	}

	@Override
	protected void writeInternal(T t, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException {
		writeInternal(t, null, outputMessage);
	}

	protected abstract void writeInternal(T t, @Nullable Type type, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException;

}

外部消息转换器绝大数都是实现这个类, 子类要实现以下方法

readInternal(Class<? extends T> clazz, HttpInputMessage inputMessage)  //写的逻辑
writeInternal(T t, HttpOutputMessage outputMessage) //读的逻辑

一、FormHttpMessageConverter

表单与MultiValueMap的相互转换/文件下载

从名字知道,它和Form表单有关。浏览器原生表单默认提交数据的方式是application/x-www-form-urlencoded;charset=utf-8
此转换器写和读支持:application/x-www-form-urlencoded”,multipart/form-data,"multipart/mixed
默认编码是utf-8,且接口参数必须是MultiValueMap
从名字知道,它和Form表单有关。浏览器原生表单默认的提交数据的方式(就是没有设置enctype属性),它默认是这个:Content-Type: application/x-www-form-urlencoded;charset=utf-8

从请求和响应读取/编写表单数据。默认情况下,它读取媒体类型 application/x-www-form-urlencoded 并将数据写入 MultiValueMap<String,String>。因为它独立的存在,所以可以看看源码内容:

public class FormHttpMessageConverter implements HttpMessageConverter<MultiValueMap<String, ?>> {
      // 缓存下它所支持的MediaType们
	private List<MediaType> supportedMediaTypes = new ArrayList<>();
     // 用于二进制内容的消息转换器们~~~ 毕竟此转换器还支持`multipart/form-data`这种  可以进行文件下载~~~~~
	private List<HttpMessageConverter<?>> partConverters = new ArrayList<>();
	
    //默认支持MediaType“application/x-www-form-urlencoded”,multipart/form-data,"multipart/mixed"
    //默认消息转换器们,ByteArrayHttpMessageConverter,StringHttpMessageConverter,ResourceHttpMessageConverter
	public FormHttpMessageConverter() {
	    //application/x-www-form-urlencoded
		this.supportedMediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED);
		//multipart/form-data
		this.supportedMediaTypes.add(MediaType.MULTIPART_FORM_DATA);
		//multipart/mixed
		this.supportedMediaTypes.add(MediaType.MULTIPART_MIXED);

		this.partConverters.add(new ByteArrayHttpMessageConverter());
		this.partConverters.add(new StringHttpMessageConverter());
		this.partConverters.add(new ResourceHttpMessageConverter());
		applyDefaultCharset();
	}
	
	@Override
	public boolean canRead(Class<?> clazz, @Nullable MediaType mediaType) {
		if (!MultiValueMap.class.isAssignableFrom(clazz)) {
			return false;
		}
		if (mediaType == null) {
			return true;
		}
		for (MediaType supportedMediaType : getSupportedMediaTypes()) {
			if (MULTIPART_ALL.includes(supportedMediaType)) {
				// We can't read multipart, so skip this supported media type.
				continue;
			}
			if (supportedMediaType.includes(mediaType)) {
				return true;
			}
		}
		return false;
	}

	@Override
	public boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType) {
		if (!MultiValueMap.class.isAssignableFrom(clazz)) {
			return false;
		}
		if (mediaType == null || MediaType.ALL.equals(mediaType)) {
			return true;
		}
		for (MediaType supportedMediaType : getSupportedMediaTypes()) {
			if (supportedMediaType.isCompatibleWith(mediaType)) {
				return true;
			}
		}
		return false;
	}

	@Override
	public MultiValueMap<String, String> read(@Nullable Class<? extends MultiValueMap<String, ?>> clazz,
			HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {

		MediaType contentType = inputMessage.getHeaders().getContentType();
		Charset charset = (contentType != null && contentType.getCharset() != null ?
				contentType.getCharset() : this.charset);
		String body = StreamUtils.copyToString(inputMessage.getBody(), charset);

		String[] pairs = StringUtils.tokenizeToStringArray(body, "&");
		MultiValueMap<String, String> result = new LinkedMultiValueMap<>(pairs.length);
		for (String pair : pairs) {
			int idx = pair.indexOf('=');
			if (idx == -1) {
				result.add(URLDecoder.decode(pair, charset.name()), null);
			}
			else {
				String name = URLDecoder.decode(pair.substring(0, idx), charset.name());
				String value = URLDecoder.decode(pair.substring(idx + 1), charset.name());
				result.add(name, value);
			}
		}
		return result;
	}

	@Override
	@SuppressWarnings("unchecked")
	public void write(MultiValueMap<String, ?> map, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException {

		if (isMultipart(map, contentType)) {
			writeMultipart((MultiValueMap<String, Object>) map, contentType, outputMessage);
		}
		else {
			writeForm((MultiValueMap<String, Object>) map, contentType, outputMessage);
		}
	}
}

但是spring默认装载消息转换器是他的子类AllEncompassingFormHttpMessageConverter,它对FormHttp…的扩展,提供了对xml和json的支持
底层是使用其他的消息转换器进行转换

public class AllEncompassingFormHttpMessageConverter extends FormHttpMessageConverter {
...
	public AllEncompassingFormHttpMessageConverter() {
		try {
		    addPartConverter(new SourceHttpMessageConverter<>());
		}
		catch (Error err) {
		}

		if (jaxb2Present && !jackson2XmlPresent) {addPartConverter(new Jaxb2RootElementHttpMessageConverter());}

		if (jackson2Present) {addPartConverter(new MappingJackson2HttpMessageConverter());}
		else if (gsonPresent) {addPartConverter(new GsonHttpMessageConverter());}
		else if (jsonbPresent) { addPartConverter(new JsonbHttpMessageConverter());}
		
		if (jackson2XmlPresent) {addPartConverter(new MappingJackson2XmlHttpMessageConverter());}
		
		if (jackson2SmilePresent) {addPartConverter(new MappingJackson2SmileHttpMessageConverter());}
	}
}

三、固定类型消息转换器

这些消息转换器都是AbstractHttpMessageConverter的子类
主要部分如下
在这里插入图片描述

1、字符消息转换器

作用是:数据与String类型的相互转换,
出参或者入参类型必须是String。MediaType没有限制

public class StringHttpMessageConverter extends AbstractHttpMessageConverter<String> {

	//转成字符串的默认编码为ISO-8859-1
	public static final Charset DEFAULT_CHARSET = StandardCharsets.ISO_8859_1;

	public StringHttpMessageConverter() {
		this(DEFAULT_CHARSET);
	}
    //默认支持的MediaType是text/plain,”*/*“
    // 因为它支持MediaType.TEXT_PLAIN, MediaType.ALL所有类型,所以你的contentType无所谓~~~ 它都能够处理
	public StringHttpMessageConverter(Charset defaultCharset) {
		super(defaultCharset, MediaType.TEXT_PLAIN, MediaType.ALL);
	}


   //如果是string类型,就进入
	@Override
	public boolean supports(Class<?> clazz) {
		return String.class == clazz;
	}

	@Override
	protected String readInternal(Class<? extends String> clazz, HttpInputMessage inputMessage) throws IOException {
		Charset charset = getContentTypeCharset(inputMessage.getHeaders().getContentType());
		/// 根据编码把字符串读进来~
		return StreamUtils.copyToString(inputMessage.getBody(), charset);
	}

	@Override
	protected Long getContentLength(String str, @Nullable MediaType contentType) {
		Charset charset = getContentTypeCharset(contentType);
		return (long) str.getBytes(charset).length;
	}

	@Override
	protected void writeInternal(String str, HttpOutputMessage outputMessage) throws IOException {
		HttpHeaders headers = outputMessage.getHeaders();
		if (this.writeAcceptCharset && headers.get(HttpHeaders.ACCEPT_CHARSET) == null) {
			headers.setAcceptCharset(getAcceptedCharsets());
		}
		// 根据编码把字符串写进去~
		Charset charset = getContentTypeCharset(headers.getContentType());
		StreamUtils.copy(str, charset, outputMessage.getBody());
	}

案例

注意必须标注@RequestBody注解,否则不会调用此消息转换器

@RequestMapping(value = "hello",method = RequestMethod.POST)
public String string( @RequestBody String son){
     System.out.println(son);
     return son.toString();
}

Content-Type:text/plain(其实Content-Type为任何值都一样),请求体是:飒飒说,控制台输出:???。
乱码?这是因为StringHttpMessageConverter读和写的编码方式是:ISO-8859-1

解决方法1

@RequestMapping(value = "hello",method = RequestMethod.POST,produces = "text/plain;charset=UTF-8")
    public String string(  @RequestBody String son){
        System.out.println(son);
        return son.toString();
    }

解决方案2

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        for(HttpMessageConverter httpMessageConverter:converters){
            if(StringHttpMessageConverter.class.isAssignableFrom(httpMessageConverter.getClass())){
                ((StringHttpMessageConverter)httpMessageConverter).setDefaultCharset(Charset.forName("UTF-8"));
            }
        }
    }
}

解决方案3 从网上找到的

这种方法只有在springboot项目中才有效

@Configuration
public class WebConfig extends WebMvcConfigurationSupport   {
    @Bean
    public HttpMessageConverter<String> responseBodyConverter() {
        return new StringHttpMessageConverter(Charset.forName("UTF-8"));
    }
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(responseBodyConverter());
        // 这里必须加上加载默认转换器,不然bug玩死人,并且该bug目前在网络上似乎没有解决方案
        // 百度,谷歌,各大论坛等。你可以试试去掉。如果这段代码,spring启动后消息转换器只有StringHttpMessageConverter这一个
        addDefaultHttpMessageConverters(converters);
    }
    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        configurer.favorPathExtension(false);
    }
}
2、数组消息转换器

作用是:数据与字节数组的相互转换,
读或写参数类型必须是byte[],MediaType没有限制

public class ByteArrayHttpMessageConverter extends AbstractHttpMessageConverter<byte[]> {

	/**
	 * //默认支持的MediaType是application/octet-stream,”*/**/
	public ByteArrayHttpMessageConverter() {
		super(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL);
	}

    //入参是byte[]
	@Override
	public boolean supports(Class<?> clazz) {
		return byte[].class == clazz;
	}

	@Override
	public byte[] readInternal(Class<? extends byte[]> clazz, HttpInputMessage inputMessage) throws IOException {
		long contentLength = inputMessage.getHeaders().getContentLength();
		ByteArrayOutputStream bos = new ByteArrayOutputStream(contentLength >= 0 ? (int) contentLength : StreamUtils.BUFFER_SIZE);
		StreamUtils.copy(inputMessage.getBody(), bos);
		return bos.toByteArray();
	}

	@Override
	protected Long getContentLength(byte[] bytes, @Nullable MediaType contentType) {
		return (long) bytes.length;
	}

	@Override
	protected void writeInternal(byte[] bytes, HttpOutputMessage outputMessage) throws IOException {
		StreamUtils.copy(bytes, outputMessage.getBody());
	}

}
3、Resource类型消息转换器

作用是:负责静态资源都解析,
读或写参数类型必须是Resource,MediaType没有限制

public class ResourceHttpMessageConverter extends AbstractHttpMessageConverter<Resource> {

	//默认支持任何MediaType类型*/*
	public ResourceHttpMessageConverter() {
		super(MediaType.ALL);
		this.supportsReadStreaming = true;
	}

	public ResourceHttpMessageConverter(boolean supportsReadStreaming) {
		super(MediaType.ALL);
		this.supportsReadStreaming = supportsReadStreaming;
	}

   //入参是Resource
	@Override
	protected boolean supports(Class<?> clazz) {
		return Resource.class.isAssignableFrom(clazz);
	}

案例

@RequestMapping(value = "hello",method = RequestMethod.POST)
public String string( @RequestBody Resource son){
        dumpStream(son);//这个方法可以取出流中的信息,并输出
        return son.toString();
}
4、XML消息转换器

注意类上必须标注@XmlRootElement注解才会被这个消息转换器解析

public class Jaxb2RootElementHttpMessageConverter extends AbstractJaxb2HttpMessageConverter<Object> {

	private boolean supportDtd = false;

	private boolean processExternalEntities = false;

	@Override
	public boolean canRead(Class<?> clazz, @Nullable MediaType mediaType) {
		return (clazz.isAnnotationPresent(XmlRootElement.class) || clazz.isAnnotationPresent(XmlType.class)) &&
				canRead(mediaType);
	}

	@Override
	public boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType) {
		return (AnnotationUtils.findAnnotation(clazz, XmlRootElement.class) != null && canWrite(mediaType));
	}

	@Override
	protected boolean supports(Class<?> clazz) {
		// should not be called, since we override canRead/Write
		throw new UnsupportedOperationException();
	}

	@Override
	protected Object readFromSource(Class<?> clazz, HttpHeaders headers, Source source) throws Exception {
		try {
			source = processSource(source);
			Unmarshaller unmarshaller = createUnmarshaller(clazz);
			if (clazz.isAnnotationPresent(XmlRootElement.class)) {
				return unmarshaller.unmarshal(source);
			}
			else {
				JAXBElement<?> jaxbElement = unmarshaller.unmarshal(source, clazz);
				return jaxbElement.getValue();
			}
		}
		catch (NullPointerException ex) {
			if (!isSupportDtd()) {
				throw new IllegalStateException("NPE while unmarshalling. " +
						"This can happen due to the presence of DTD declarations which are disabled.", ex);
			}
			throw ex;
		}
		catch (UnmarshalException ex) {
			throw ex;
		}
		catch (JAXBException ex) {
			throw new HttpMessageConversionException("Invalid JAXB setup: " + ex.getMessage(), ex);
		}
	}

	@SuppressWarnings("deprecation")  // on JDK 9
	protected Source processSource(Source source) {
		if (source instanceof StreamSource) {
			StreamSource streamSource = (StreamSource) source;
			InputSource inputSource = new InputSource(streamSource.getInputStream());
			try {
				XMLReader xmlReader = org.xml.sax.helpers.XMLReaderFactory.createXMLReader();
				xmlReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", !isSupportDtd());
				String featureName = "http://xml.org/sax/features/external-general-entities";
				xmlReader.setFeature(featureName, isProcessExternalEntities());
				if (!isProcessExternalEntities()) {
					xmlReader.setEntityResolver(NO_OP_ENTITY_RESOLVER);
				}
				return new SAXSource(xmlReader, inputSource);
			}
			catch (SAXException ex) {
				logger.warn("Processing of external entities could not be disabled", ex);
				return source;
			}
		}
		else {
			return source;
		}
	}

	@Override
	protected void writeToResult(Object o, HttpHeaders headers, Result result) throws Exception {
		try {
			Class<?> clazz = ClassUtils.getUserClass(o);
			Marshaller marshaller = createMarshaller(clazz);
			setCharset(headers.getContentType(), marshaller);
			marshaller.marshal(o, result);
		}
		catch (MarshalException ex) {
			throw ex;
		}
		catch (JAXBException ex) {
			throw new HttpMessageConversionException("Invalid JAXB setup: " + ex.getMessage(), ex);
		}
	}

	private void setCharset(@Nullable MediaType contentType, Marshaller marshaller) throws PropertyException {
		if (contentType != null && contentType.getCharset() != null) {
			marshaller.setProperty(Marshaller.JAXB_ENCODING, contentType.getCharset().name());
		}
	}


	private static final EntityResolver NO_OP_ENTITY_RESOLVER =
			(publicId, systemId) -> new InputSource(new StringReader(""));

}

三、Jackson消息转化器

AbstractJackson2HttpMessageConverter是所有Jackson消息转化器的基类

public abstract class AbstractJackson2HttpMessageConverter extends AbstractGenericHttpMessageConverter<Object> {
	protected ObjectMapper objectMapper;
	@Nullable
	private Boolean prettyPrint;
	@Nullable
	private PrettyPrinter ssePrettyPrinter;

	@Override
	public boolean canRead(Type type, @Nullable Class<?> contextClass, @Nullable MediaType mediaType) {
	    //必须mediaType支持
		if (!canRead(mediaType)) {
			return false;
		}
		//方法参数参数类型
		JavaType javaType = getJavaType(type, contextClass);
		//错误信息
		AtomicReference<Throwable> causeRef = new AtomicReference<>();
		//如果支持反序列化此种类型
		if (this.objectMapper.canDeserialize(javaType, causeRef)) {
			return true;
		}
		//日志打印
		logWarningIfNecessary(javaType, causeRef.get());
		return false;
	}

	@Override
	public boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType) {
	    //类型支持
		if (!canWrite(mediaType)) {
			return false;
		}
		AtomicReference<Throwable> causeRef = new AtomicReference<>();
		//如果支持序列化此种类型
		if (this.objectMapper.canSerialize(clazz, causeRef)) {
			return true;
		}
		//日志打印
		logWarningIfNecessary(clazz, causeRef.get());
		return false;
	}

	@Override
	protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage)
			throws IOException, HttpMessageNotReadableException {
       
		JavaType javaType = getJavaType(clazz, null);
		return readJavaType(javaType, inputMessage);
	}

	@Override
	public Object read(Type type, @Nullable Class<?> contextClass, HttpInputMessage inputMessage)
			throws IOException, HttpMessageNotReadableException {

		JavaType javaType = getJavaType(type, contextClass);
		return readJavaType(javaType, inputMessage);
	}

	private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) throws IOException {
		try {
			if (inputMessage instanceof MappingJacksonInputMessage) {
				Class<?> deserializationView = ((MappingJacksonInputMessage) inputMessage).getDeserializationView();
				if (deserializationView != null) {
					return this.objectMapper.readerWithView(deserializationView).forType(javaType).
							readValue(inputMessage.getBody());
				}
			}
			return this.objectMapper.readValue(inputMessage.getBody(), javaType);
		}
		catch (InvalidDefinitionException ex) {
			throw new HttpMessageConversionException("Type definition error: " + ex.getType(), ex);
		}
		catch (JsonProcessingException ex) {
			throw new HttpMessageNotReadableException("JSON parse error: " + ex.getOriginalMessage(), ex, inputMessage);
		}
	}

	@Override
	protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException {

		MediaType contentType = outputMessage.getHeaders().getContentType();
		JsonEncoding encoding = getJsonEncoding(contentType);
		JsonGenerator generator = this.objectMapper.getFactory().createGenerator(outputMessage.getBody(), encoding);
		try {
		    
			writePrefix(generator, object);
			Object value = object;
			Class<?> serializationView = null;
			FilterProvider filters = null;
			JavaType javaType = null;

			if (object instanceof MappingJacksonValue) {
				MappingJacksonValue container = (MappingJacksonValue) object;
				value = container.getValue();
				serializationView = container.getSerializationView();
				filters = container.getFilters();
			}
			if (type != null && TypeUtils.isAssignable(type, value.getClass())) {
				javaType = getJavaType(type, null);
			}

			ObjectWriter objectWriter = (serializationView != null ?
					this.objectMapper.writerWithView(serializationView) : this.objectMapper.writer());
			if (filters != null) {
				objectWriter = objectWriter.with(filters);
			}
			if (javaType != null && javaType.isContainerType()) {
				objectWriter = objectWriter.forType(javaType);
			}
			SerializationConfig config = objectWriter.getConfig();
			if (contentType != null && contentType.isCompatibleWith(MediaType.TEXT_EVENT_STREAM) &&
					config.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
				objectWriter = objectWriter.with(this.ssePrettyPrinter);
			}
			objectWriter.writeValue(generator, value);

			writeSuffix(generator, object);
			generator.flush();
		}
		catch (InvalidDefinitionException ex) {
			throw new HttpMessageConversionException("Type definition error: " + ex.getType(), ex);
		}
		catch (JsonProcessingException ex) {
			throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getOriginalMessage(), ex);
		}
	}
	
	protected void writePrefix(JsonGenerator generator, Object object) throws IOException {}
	protected void writeSuffix(JsonGenerator generator, Object object) throws IOException {}
}
1、JSON消息转换器

作用:JSON和JAVA对象的相互转换
MediaType必须是application/json

public class MappingJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter {
    //Jackson转换器他俩都是支持jsonPrefix我们可以自定义Json前缀的~~~
	@Nullable
	private String jsonPrefix;

	public MappingJackson2HttpMessageConverter() {
		this(Jackson2ObjectMapperBuilder.json().build());
	}

	public MappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
		super(objectMapper, MediaType.APPLICATION_JSON, new MediaType("application", "*+json"));
	}

	@Override
	protected void writePrefix(JsonGenerator generator, Object object) throws IOException {
		if (this.jsonPrefix != null) {
			generator.writeRaw(this.jsonPrefix);
		}
	}

}
2、XML消息转换器

作用:XML和JAVA对象的相互转换
MediaType必须是application/xml或者text/xml

这个转化器必须需要额外导包Jackson-dataformat-XML才能生效。从Spring4.1后才有

public class MappingJackson2XmlHttpMessageConverter extends AbstractJackson2HttpMessageConverter {

	public MappingJackson2XmlHttpMessageConverter() {
		this(Jackson2ObjectMapperBuilder.xml().build());
	}

	public MappingJackson2XmlHttpMessageConverter(ObjectMapper objectMapper) {
		super(objectMapper, new MediaType("application", "xml", StandardCharsets.UTF_8),
				new MediaType("text", "xml", StandardCharsets.UTF_8),
				new MediaType("application", "*+xml", StandardCharsets.UTF_8));
		Assert.isInstanceOf(XmlMapper.class, objectMapper, "XmlMapper required");
	}

	@Override
	public void setObjectMapper(ObjectMapper objectMapper) {
		Assert.isInstanceOf(XmlMapper.class, objectMapper, "XmlMapper required");
		super.setObjectMapper(objectMapper);
	}

}

四、Spring MVC默认注册哪些HttpMessageConverter?

说明:此处情况完全以Spring MVC版本讲解,和Spring Boot无关。
Spring 版本号为:5.2.1.RELEASE

1、不开启注解@EnableWebMvc

@Configuration
public class WebConfig  {
 ...
}

`springMVC项目会默认注册4个消息转换器,他们是

ByteArrayHttpMessageConverter      StringHttpMessageConverter,
SourceHttpMessageConverter         AllEncompassingFormHttpMessageConverter

2、开启注解@EnableWebMvc

@Configuration
@EnableWebMvc
public class WebConfig  {
 ...
}

`springMVC项目会默认注册7个消息转换器,他们是

AllEncompassingFormHttpMessageConverter    StringHttpMessageConverter,
ByteArrayHttpMessageConverter              ResourceHttpMessageConverter,
ResourceRegionHttpMessageConverter         SourceHttpMessageConverter
Jaxb2RootElementHttpMessageConverter

注意;若我们classpath下有Jackson的包,那就会默认装配MappingJackson2HttpMessageConverter。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值