簡(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)调卑。
一些小細(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)的。