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