HttpMessageConverter

作者: 小弦弦喵喵喵 | 来源:发表于2018-01-15 13:06 被阅读95次

MIME类型

MIME(Multipurpose Internet Mail Extensions)多用途互联网邮件扩展类型。是设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开。多用于指定一些客户端自定义的文件名,以及一些媒体文件打开方式。

在万维网中使用的HTTP协议中也使用了MIME的框架,它使得HTTP传输的不仅是普通的文本,而变得丰富多彩。

在HTTP中,MIME类型被定义在Content-Type header中。

image image

HttpMessageConverter简介

HTTP 请求和响应的传输是字节流,意味着浏览器和服务器通过字节流进行通信。但是,使用 Spring,controller 类中的方法返回纯 String 类型或其他 Java 内建对象。如何将对象转换成字节流进行传输?

在报文到达SpringMVC和从SpringMVC出去,都存在一个字节流到java对象的转换问题。在SpringMVC中,它是由HttpMessageConverter来处理的。

当请求报文来到java中,它会被封装成为一个ServletInputStream的输入流,供我们读取报文。响应报文则是通过一个ServletOutputStream的输出流,来输出响应报文。

我们可以用下图来加深理解。

image

HttpMessageConverter接口

在Spring中,内置了大量的HttpMessageConverter。通过请求头信息中的MIME类型,选择相应的HttpMessageConverter。

它们都实现了HttpMessageConverter这个接口。

接口的代码如下


public interface HttpMessageConverter<T> {

  booleancanRead(Class<?>clazz, MediaTypemediaType);

  booleancanWrite(Class<?>clazz, MediaTypemediaType);

  List<MediaType>getSupportedMediaTypes();

  T read(Class<? extends T>clazz, HttpInputMessageinputMessage)

      throws IOException, HttpMessageNotReadableException;

   void write(T t, MediaTypecontentType, HttpOutputMessageoutputMessage)

      throws IOException, HttpMessageNotWritableException;

}

HttpMessageConverter接口的定义中出现了成对的canRead(),read()和canWrite(),write()方法。MediaType是对请求的Media Type属性的封装。

read方法中有一个HttpInputMessage,我们查看它的源码如下。

public interface HttpInputMessageextends HttpMessage {
  InputStreamgetBody() throws IOException;
}

HttpInputMessage提供的接口就是将body中的数据转为输入流。
write方法中有一个HttpOutputMessage,我们查看它的源码如下。

public interface HttpOutputMessageextends HttpMessage {
  OutputStreamgetBody() throws IOException;
}

HttpOutputMessage提供的接口就是将body中的数据转为输出流。
它们拥有相同的父接口HttpMessage。

public interface HttpMessage {
  HttpHeadersgetHeaders();
}

HttpMessage提供的方法是读取头部中的信息。

HttpMessageConverter的工作过程

当我们声明了下面这个处理方法。

@RequestMapping(value="/string", method=RequestMethod.POST)
public @ResponseBodyString readString(@RequestBody String string) {
  return "Read string '" + string + "'";
}

在SpringMVC进入readString方法前,会根据@RequestBody注解选择适当的HttpMessageConverter实现类来将请求参数解析到string变量中,具体来说是使用了StringHttpMessageConverter类,它的canRead()方法返回true,然后它的read()方法会从请求中读出请求参数,绑定到readString()方法的string变量中。
当SpringMVC执行readString方法后,由于返回值标识了@ResponseBody,SpringMVC将使用StringHttpMessageConverter的write()方法,将结果作为String值写入响应报文,当然,此时canWrite()方法返回true。

Spring中内置的一部分HttpMessageConverter如下。


各种HttpMessageConverter

处理请求时,由合适的消息转换器将请求报文绑定为方法中的形参对象,在这里,同一个对象就有可能出现多种不同的消息形式,比如json和xml。同样,当响应请求时,方法的返回值也同样可能被返回为不同的消息形式,比如json和xml。
在SpringMVC中,针对不同的消息形式,我们有不同的HttpMessageConverter实现类来处理各种消息形式。至于各种消息间解析细节的不同,就被屏蔽在不同的HttpMessageConverter实现类中了。

自定义HttpMessageConverter

在颠覆者中,我们进行了自定义HttpMessageConverter,当时我们是这么做的。

public class MyMessageConverterextends AbstractHttpMessageConverter<DemoObj> 

查看AbstractHttpMessageConverter的源码。

public abstract class AbstractHttpMessageConverter<T>implements HttpMessageConverter<T>

可以看到实现了HttpMessageConverter接口,可以说这个接口是HttpMessageConverter的核心。

@Override
protected DemoObjreadInternal(Class<? extends DemoObj>clazz,
HttpInputMessageinputMessage) throws IOException,
HttpMessageNotReadableException {
  String temp = StreamUtils.copyToString(inputMessage.getBody(),
  Charset.forName("UTF-8"));
  String[] tempArr = temp.split("-");
  return new DemoObj(new Long(tempArr[0]), tempArr[1]);
}

读进来转换成对象。

@Override
protected void writeInternal(DemoObjobj, HttpOutputMessageoutputMessage)
throws IOException, HttpMessageNotWritableException {
  String out = "hello:" + obj.getId() + "-"
  + obj.getName();
  outputMessage.getBody().write(out.getBytes());
}

将对象输出去。

问题

在ConverterController中定义了映射"/convert"。
然而,在地址栏中访问http://localhost:8080/convert?id=2&name=wang会报错“Required request body content is missing:……”,为什么?如何避免这一错误?

问题解决

原因是在地址栏中输入url进行访问是get请求,
而@RequestBody注解对get请求并不适用,而是要将参数放在请求体中。注释掉@RequestBody注解即可避免这一错误。
但是注释掉@RequestBody注解后,会发现返回的信息被下载下来了。

@RequestMapping(value = "/convert", produces = { "application/x-wisely" })

这是因为响应的格式被定义成了我们自定义的类型,而在MyMessageConverter中输出到outPutMessage时我们并没有设置Content-type。

@Override
protected void writeInternal(DemoObj obj, HttpOutputMessage outputMessage)
    throws IOException, HttpMessageNotWritableException{
    String out  = "hello:" + obj.getId() + "-"+ obj.getName();
    outputMessage.getHeaders().setContentType(MediaType.TEXT_PLAIN);
    outputMessage.getBody().write(out.getBytes());
}

这样设置后就能显示在浏览器页面上了。

另一种解决方案

使用@RequestBody注解,发送POST请求,把参数放在request体中,用例子中的converter页面,用ajax发送post请求后成功回调函数中把获取的data输出到页面上,这样即使没有设置HttpOutputMessage响应头的content-type也能把信息输出到页面上。

  function req(){
        $.ajax({
            url: "convert",
            data: "1-wangyunfei", //1
            type:"POST",
            contentType:"application/x-wisely", //2
            success: function(data){
                $("#resp").html(data);
            }
        });
    }
运行效果

相关文章

网友评论

    本文标题:HttpMessageConverter

    本文链接:https://www.haomeiwen.com/subject/vbiroxtx.html