springMVC的消息轉(zhuǎn)換器(Message Converter)

簡(jiǎn)介

請(qǐng)求和響應(yīng)都有對(duì)應(yīng)的body骤视,而這個(gè)body就是需要關(guān)注的主要數(shù)據(jù)辜羊。

請(qǐng)求體與請(qǐng)求的查詢參數(shù)或者表單參數(shù)是不同的刽射,請(qǐng)求體的表述一般就是一段字符串轻掩,而查詢參數(shù)可以看作url的一部分织咧,這兩個(gè)是位于請(qǐng)求報(bào)文的不同地方胀葱。表單參數(shù)可以按照一定格式放在請(qǐng)求體中,也可以放在url上作為查詢參數(shù)笙蒙〉钟欤總之可以把請(qǐng)求體看作客戶端通過(guò)請(qǐng)求報(bào)文捎帶的字符串。

響應(yīng)體則是瀏覽器渲染頁(yè)面的依據(jù)捅位,對(duì)于一個(gè)普通html頁(yè)面得響應(yīng)轧葛,響應(yīng)體就是這個(gè)html頁(yè)面的源代碼搂抒。

請(qǐng)求體和響應(yīng)體都是需要配合Content-Type頭部使用的,這個(gè)頭部主要用于說(shuō)明body中得字符串是什么格式的尿扯,比如:text求晶,json,xml等衷笋。對(duì)于請(qǐng)求報(bào)文芳杏,只有通過(guò)此頭部,服務(wù)器才能知道怎么解析請(qǐng)求體中的字符串右莱,對(duì)于響應(yīng)報(bào)文蚜锨,瀏覽器通過(guò)此頭部才知道應(yīng)該怎么渲染響應(yīng)結(jié)果,是直接打印字符串還是根據(jù)代碼渲染為一個(gè)網(wǎng)頁(yè)慢蜓。

還有一個(gè)與body有關(guān)的頭部是Accept亚再,這個(gè)頭部標(biāo)識(shí)了客戶端期望得到什么格式的響應(yīng)體。服務(wù)器可根據(jù)此字段選擇合適的結(jié)果表述晨抡。

對(duì)于HttpServletRequest和HttpServletResponse氛悬,可以分別調(diào)用getInputStream和getOutputStream來(lái)直接獲取body。但是獲取到的僅僅只是一段字符串耘柱,而對(duì)于java來(lái)說(shuō)如捅,處理一個(gè)對(duì)象肯定比處理一個(gè)字符串要方便得多,也好理解得多调煎。所以根據(jù)Content-Type頭部镜遣,將body字符串轉(zhuǎn)換為java對(duì)象是常有的事。反過(guò)來(lái)士袄,根據(jù)Accept頭部悲关,將java對(duì)象轉(zhuǎn)換客戶端期望格式的字符串也是必不可少的工作。

spring消息轉(zhuǎn)換器源碼簡(jiǎn)要分析

而springMVC為我們提供了一系列默認(rèn)的消息轉(zhuǎn)換器娄柳。

對(duì)于消息轉(zhuǎn)換器的調(diào)用寓辱,都是在RequestResponseBodyMethodProcessor類中完成的。它實(shí)現(xiàn)了HandlerMethodArgumentResolver和HandlerMethodReturnValueHandler兩個(gè)接口赤拒,分別實(shí)現(xiàn)了處理參數(shù)和處理返回值的方法秫筏。

而要?jiǎng)佑眠@些消息轉(zhuǎn)換器,需要在特定的位置加上@RequestBody和@ResponseBody挎挖。

/**
 * RequestResponseBodyMethodProcessor.class
 */
···
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(RequestBody.class);
    }

    public boolean supportsReturnType(MethodParameter returnType) {
        return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
                returnType.hasMethodAnnotation(ResponseBody.class));
    }
···

對(duì)返回值的消息轉(zhuǎn)換來(lái)說(shuō):

/**
 * RequestResponseBodyMethodProcessor.class
 */
···
public void handleReturnValue(Object returnValue, MethodParameter returnType,
            ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
            throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException;

大致流程為:
1.根據(jù)返回值獲取其類型这敬,其中MethodParameter封裝了方法對(duì)象,可獲去方法返回值類型蕉朵。

/**
 * AbstractMessageConverterMethodProcessor.class
 */
···
            outputValue = value;
            valueType = getReturnValueType(outputValue, returnType);
            declaredType = getGenericType(returnType);

2.根據(jù)request的Accept和HandellerMapping的produces屬性經(jīng)過(guò)比對(duì)鹅颊、排序從而得到最應(yīng)該轉(zhuǎn)換的消息格式(MediaType)。

/**
 * AbstractMessageConverterMethodProcessor.class
 */
···
        List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);
        List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);
        Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
···
//匹配
···
        List<MediaType> mediaTypes = new ArrayList<MediaType>(compatibleMediaTypes);
        MediaType.sortBySpecificityAndQuality(mediaTypes);

        MediaType selectedMediaType = null;
···
//選擇最具體的MediaType
···

3.遍歷所有已配置的消息轉(zhuǎn)換器墓造,調(diào)用其canWrite方法堪伍,根據(jù)返回值類型(valueType)和消息格式(MediaType)來(lái)檢測(cè)是否可以轉(zhuǎn)換。

/**
 * AbstractMessageConverterMethodProcessor.class
 */
···
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
···
     if (messageConverter.canWrite(valueType, selectedMediaType)){
···
        ((HttpMessageConverter) messageConverter).write(outputValue, selectedMediaType, outputMessage);
···
     }
}

4.若有對(duì)應(yīng)的轉(zhuǎn)換器觅闽,則執(zhí)行消息轉(zhuǎn)換帝雇,即write方法。在write方法中蛉拙,將返回值被轉(zhuǎn)換后得到的字符串寫(xiě)在Response的輸出流中尸闸。若找不到對(duì)應(yīng)的轉(zhuǎn)換器,則拋出HttpMediaTypeNotAcceptableException異常孕锄,瀏覽器會(huì)收到一個(gè)406 Not Acceptable狀態(tài)碼吮廉。

自定義消息轉(zhuǎn)換器

除了spring提供的9個(gè)默認(rèn)的消息轉(zhuǎn)換器,還可以添加自定義的消息轉(zhuǎn)換器畸肆,或者更換消息轉(zhuǎn)換器的實(shí)現(xiàn)宦芦。

一個(gè)自定義消息轉(zhuǎn)換器的例子:
該例子旨在將json轉(zhuǎn)換器替換為fastjson實(shí)現(xiàn),xml轉(zhuǎn)換器替換為jackson-dataformat-xml實(shí)現(xiàn)轴脐。

首先添加依賴:

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.17</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
            <version>2.8.7</version>
        </dependency>

配置類:

@Configuration
public class Cfg_Web {
    //message converter
    @Bean
    public HttpMessageConverters messageConverters(){
        //json
        FastJsonHttpMessageConverter jsonMessageConverter = new FastJsonHttpMessageConverter();
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        fastJsonConfig.setCharset(Charset.forName("utf-8"));
        jsonMessageConverter.setFastJsonConfig(fastJsonConfig);
        List<MediaType> jsonMediaTypes = new ArrayList<>();
        jsonMediaTypes.add(MediaType.APPLICATION_JSON);
        jsonMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
        jsonMessageConverter.setSupportedMediaTypes(jsonMediaTypes);

        //xml
        MappingJackson2XmlHttpMessageConverter xmlMessageConverter = new MappingJackson2XmlHttpMessageConverter();
        xmlMessageConverter.setObjectMapper(new XmlMapper());
        xmlMessageConverter.setDefaultCharset(Charset.forName("utf-8"));
        List<MediaType> xmlMediaTypes = new ArrayList<>();
        xmlMediaTypes.add(MediaType.APPLICATION_XML);
        xmlMediaTypes.add(MediaType.TEXT_XML);
        xmlMessageConverter.setSupportedMediaTypes(xmlMediaTypes);

        return new HttpMessageConverters(Arrays.asList(jsonMessageConverter, xmlMessageConverter));
    }
}

測(cè)試使用:

public class Student {
    private String code;
    private String name;
···//省略set和get方法
    @Override
    public String toString() {
        return "Student{" +
                "code='" + code + '\'' +
                ", name='" + name + '\'' +
                '}';
    }
}
@Controller
public class TestController {

    @RequestMapping(value = "json", produces = MediaType.APPLICATION_XML_VALUE)
    @ResponseBody
    public Object jsonTest(@RequestBody Student student){
        System.out.println(student);

        return student;
    }

    @RequestMapping(value = "xml", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    @ResponseBody
    public Object xmlTest(@RequestBody Student student){
        System.out.println(student);

        return student;
    }
}

這個(gè)測(cè)試主要是將json格式和xml格式請(qǐng)求響應(yīng)互轉(zhuǎn)调卑。

json -> xml
xml -> json

一些小細(xì)節(jié)

1.如果一個(gè)Controller類里面所有方法的返回值都需要經(jīng)過(guò)消息轉(zhuǎn)換器,那么可以在類上面加上@ResponseBody注解或者將@Controller注解修改為@RestController注解大咱,這樣做就相當(dāng)于在每個(gè)方法都加上了@ResponseBody注解了恬涧。
2.@ResponseBody@RequestBody都可以處理Map類型的對(duì)象。如果不確定參數(shù)的具體字段碴巾,可以用Map接收溯捆。@ReqeustBody同樣適用。
3.方法上的和類上的@ResponseBody都可以被繼承厦瓢。
4.默認(rèn)的xml轉(zhuǎn)換器Jaxb2RootElementHttpMessageConverter需要類上有@XmlRootElement注解才能被轉(zhuǎn)換提揍。

/**
* Jaxb2RootElementHttpMessageConverter.class
*/
    @Override
    public boolean canWrite(Class<?> clazz, MediaType mediaType) {
        return (AnnotationUtils.findAnnotation(clazz, XmlRootElement.class) != null && canWrite(mediaType));
    }

5.返回值類型可聲明為基類的類型,不影響轉(zhuǎn)換旷痕,但參數(shù)的類型必需為特定的類型碳锈。這是顯而易見(jiàn)的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末欺抗,一起剝皮案震驚了整個(gè)濱河市售碳,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌绞呈,老刑警劉巖贸人,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異佃声,居然都是意外死亡艺智,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門圾亏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)十拣,“玉大人封拧,你說(shuō)我怎么就攤上這事∝参剩” “怎么了泽西?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)缰趋。 經(jīng)常有香客問(wèn)我捧杉,道長(zhǎng),這世上最難降的妖魔是什么秘血? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任味抖,我火速辦了婚禮,結(jié)果婚禮上灰粮,老公的妹妹穿的比我還像新娘仔涩。我一直安慰自己,他們只是感情好谋竖,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布红柱。 她就那樣靜靜地躺著,像睡著了一般蓖乘。 火紅的嫁衣襯著肌膚如雪锤悄。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,036評(píng)論 1 285
  • 那天嘉抒,我揣著相機(jī)與錄音零聚,去河邊找鬼。 笑死些侍,一個(gè)胖子當(dāng)著我的面吹牛隶症,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播岗宣,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼蚂会,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了耗式?” 一聲冷哼從身側(cè)響起胁住,我...
    開(kāi)封第一講書(shū)人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎刊咳,沒(méi)想到半個(gè)月后彪见,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡娱挨,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年余指,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片跷坝。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡酵镜,死狀恐怖碉碉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情笋婿,我是刑警寧澤誉裆,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站缸濒,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏粱腻。R本人自食惡果不足惜庇配,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望绍些。 院中可真熱鬧捞慌,春花似錦、人聲如沸柬批。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)氮帐。三九已至嗅虏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間上沐,已是汗流浹背皮服。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留参咙,地道東北人龄广。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像蕴侧,于是被迫代替她去往敵國(guó)和親择同。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

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