HttpMessageConverter

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);
            }
        });
    }
运行效果
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容