前言
- 为何需要消息转换器
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。