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中拷沸。
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)加深理解喉脖。
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如下。
處理請(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);
}
});
}