HttpMessageConverter

MIME類型

MIME(Multipurpose Internet Mail Extensions)多用途互聯(lián)網(wǎng)郵件擴(kuò)展類型宰啦。是設(shè)定某種擴(kuò)展名的文件用一種應(yīng)用程序來(lái)打開的方式類型腔呜,當(dāng)該擴(kuò)展名文件被訪問(wèn)的時(shí)候哩治,瀏覽器會(huì)自動(dòng)使用指定應(yīng)用程序來(lái)打開脆栋。多用于指定一些客戶端自定義的文件名镣煮,以及一些媒體文件打開方式埃跷。

在萬(wàn)維網(wǎng)中使用的HTTP協(xié)議中也使用了MIME的框架金度,它使得HTTP傳輸?shù)牟粌H是普通的文本本讥,而變得豐富多彩珊泳。

在HTTP中,MIME類型被定義在Content-Type header中拷沸。

image
image

HttpMessageConverter簡(jiǎn)介

HTTP 請(qǐng)求和響應(yīng)的傳輸是字節(jié)流色查,意味著瀏覽器和服務(wù)器通過(guò)字節(jié)流進(jìn)行通信。但是撞芍,使用 Spring秧了,controller 類中的方法返回純 String 類型或其他 Java 內(nèi)建對(duì)象。如何將對(duì)象轉(zhuǎn)換成字節(jié)流進(jìn)行傳輸序无?

在報(bào)文到達(dá)SpringMVC和從SpringMVC出去验毡,都存在一個(gè)字節(jié)流到j(luò)ava對(duì)象的轉(zhuǎn)換問(wèn)題。在SpringMVC中帝嗡,它是由HttpMessageConverter來(lái)處理的晶通。

當(dāng)請(qǐng)求報(bào)文來(lái)到j(luò)ava中,它會(huì)被封裝成為一個(gè)ServletInputStream的輸入流哟玷,供我們讀取報(bào)文狮辽。響應(yīng)報(bào)文則是通過(guò)一個(gè)ServletOutputStream的輸出流,來(lái)輸出響應(yīng)報(bào)文巢寡。

我們可以用下圖來(lái)加深理解喉脖。

image

HttpMessageConverter接口

在Spring中,內(nèi)置了大量的HttpMessageConverter讼渊。通過(guò)請(qǐng)求頭信息中的MIME類型,選擇相應(yīng)的HttpMessageConverter尊剔。

它們都實(shí)現(xiàn)了HttpMessageConverter這個(gè)接口爪幻。

接口的代碼如下


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接口的定義中出現(xiàn)了成對(duì)的canRead(),read()和canWrite()须误,write()方法挨稿。MediaType是對(duì)請(qǐng)求的Media Type屬性的封裝。

read方法中有一個(gè)HttpInputMessage京痢,我們查看它的源碼如下奶甘。

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

HttpInputMessage提供的接口就是將body中的數(shù)據(jù)轉(zhuǎn)為輸入流。
write方法中有一個(gè)HttpOutputMessage祭椰,我們查看它的源碼如下臭家。

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

HttpOutputMessage提供的接口就是將body中的數(shù)據(jù)轉(zhuǎn)為輸出流疲陕。
它們擁有相同的父接口HttpMessage。

public interface HttpMessage {
  HttpHeadersgetHeaders();
}

HttpMessage提供的方法是讀取頭部中的信息钉赁。

HttpMessageConverter的工作過(guò)程

當(dāng)我們聲明了下面這個(gè)處理方法蹄殃。

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

在SpringMVC進(jìn)入readString方法前,會(huì)根據(jù)@RequestBody注解選擇適當(dāng)?shù)腍ttpMessageConverter實(shí)現(xiàn)類來(lái)將請(qǐng)求參數(shù)解析到string變量中你踩,具體來(lái)說(shuō)是使用了StringHttpMessageConverter類诅岩,它的canRead()方法返回true,然后它的read()方法會(huì)從請(qǐng)求中讀出請(qǐng)求參數(shù)带膜,綁定到readString()方法的string變量中吩谦。
當(dāng)SpringMVC執(zhí)行readString方法后,由于返回值標(biāo)識(shí)了@ResponseBody膝藕,SpringMVC將使用StringHttpMessageConverter的write()方法式廷,將結(jié)果作為String值寫入響應(yīng)報(bào)文,當(dāng)然束莫,此時(shí)canWrite()方法返回true懒棉。

Spring中內(nèi)置的一部分HttpMessageConverter如下。


各種HttpMessageConverter

處理請(qǐng)求時(shí)览绿,由合適的消息轉(zhuǎn)換器將請(qǐng)求報(bào)文綁定為方法中的形參對(duì)象策严,在這里,同一個(gè)對(duì)象就有可能出現(xiàn)多種不同的消息形式饿敲,比如json和xml妻导。同樣,當(dāng)響應(yīng)請(qǐng)求時(shí)怀各,方法的返回值也同樣可能被返回為不同的消息形式倔韭,比如json和xml。
在SpringMVC中瓢对,針對(duì)不同的消息形式寿酌,我們有不同的HttpMessageConverter實(shí)現(xiàn)類來(lái)處理各種消息形式。至于各種消息間解析細(xì)節(jié)的不同硕蛹,就被屏蔽在不同的HttpMessageConverter實(shí)現(xiàn)類中了醇疼。

自定義HttpMessageConverter

在顛覆者中,我們進(jìn)行了自定義HttpMessageConverter法焰,當(dāng)時(shí)我們是這么做的秧荆。

public class MyMessageConverterextends AbstractHttpMessageConverter<DemoObj> 

查看AbstractHttpMessageConverter的源碼。

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

可以看到實(shí)現(xiàn)了HttpMessageConverter接口埃仪,可以說(shuō)這個(gè)接口是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]);
}

讀進(jìn)來(lái)轉(zhuǎn)換成對(duì)象。

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

將對(duì)象輸出去卵蛉。

問(wèn)題

在ConverterController中定義了映射"/convert"颁股。
然而么库,在地址欄中訪問(wèn)http://localhost:8080/convert?id=2&name=wang會(huì)報(bào)錯(cuò)“Required request body content is missing:……”,為什么豌蟋?如何避免這一錯(cuò)誤廊散?

問(wèn)題解決

原因是在地址欄中輸入url進(jìn)行訪問(wèn)是get請(qǐng)求,
而@RequestBody注解對(duì)get請(qǐng)求并不適用梧疲,而是要將參數(shù)放在請(qǐng)求體中允睹。注釋掉@RequestBody注解即可避免這一錯(cuò)誤。
但是注釋掉@RequestBody注解后幌氮,會(huì)發(fā)現(xiàn)返回的信息被下載下來(lái)了缭受。

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

這是因?yàn)轫憫?yīng)的格式被定義成了我們自定義的類型,而在MyMessageConverter中輸出到outPutMessage時(shí)我們并沒有設(shè)置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());
}

這樣設(shè)置后就能顯示在瀏覽器頁(yè)面上了米者。

另一種解決方案

使用@RequestBody注解,發(fā)送POST請(qǐng)求宇智,把參數(shù)放在request體中蔓搞,用例子中的converter頁(yè)面,用ajax發(fā)送post請(qǐng)求后成功回調(diào)函數(shù)中把獲取的data輸出到頁(yè)面上随橘,這樣即使沒有設(shè)置HttpOutputMessage響應(yīng)頭的content-type也能把信息輸出到頁(yè)面上喂分。

  function req(){
        $.ajax({
            url: "convert",
            data: "1-wangyunfei", //1
            type:"POST",
            contentType:"application/x-wisely", //2
            success: function(data){
                $("#resp").html(data);
            }
        });
    }
運(yùn)行效果
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市机蔗,隨后出現(xiàn)的幾起案子蒲祈,更是在濱河造成了極大的恐慌,老刑警劉巖萝嘁,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件梆掸,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡牙言,警方通過(guò)查閱死者的電腦和手機(jī)酸钦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)咱枉,“玉大人卑硫,你說(shuō)我怎么就攤上這事∨痈郑” “怎么了拔恰?”我有些...
    開封第一講書人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵因谎,是天一觀的道長(zhǎng)基括。 經(jīng)常有香客問(wèn)我,道長(zhǎng)财岔,這世上最難降的妖魔是什么风皿? 我笑而不...
    開封第一講書人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任河爹,我火速辦了婚禮,結(jié)果婚禮上桐款,老公的妹妹穿的比我還像新娘咸这。我一直安慰自己,他們只是感情好魔眨,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開白布媳维。 她就那樣靜靜地躺著,像睡著了一般遏暴。 火紅的嫁衣襯著肌膚如雪侄刽。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,071評(píng)論 1 285
  • 那天朋凉,我揣著相機(jī)與錄音州丹,去河邊找鬼。 笑死杂彭,一個(gè)胖子當(dāng)著我的面吹牛墓毒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播亲怠,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼所计,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了赁炎?” 一聲冷哼從身側(cè)響起醉箕,我...
    開封第一講書人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎徙垫,沒想到半個(gè)月后讥裤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡姻报,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年己英,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吴旋。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡损肛,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出荣瑟,到底是詐尸還是另有隱情治拿,我是刑警寧澤,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布笆焰,位于F島的核電站劫谅,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜捏检,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一荞驴、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧贯城,春花似錦熊楼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至踩晶,卻和暖如春挎峦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背合瓢。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工坦胶, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人晴楔。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓顿苇,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親税弃。 傳聞我的和親對(duì)象是個(gè)殘疾皇子纪岁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容