SpringMVC源碼解析之消息轉(zhuǎn)換器HttpMessageConverter
摘要
SpringMVC使用消息轉(zhuǎn)換器實(shí)現(xiàn)請(qǐng)求報(bào)文和對(duì)象、對(duì)象和響應(yīng)報(bào)文之間的自動(dòng)轉(zhuǎn)換
在SpringMVC中,可以使用@RequestBody和@ResponseBody兩個(gè)注解,分別完成請(qǐng)求報(bào)文到對(duì)象和對(duì)象到響應(yīng)報(bào)文的轉(zhuǎn)換,底層這種靈活的消息轉(zhuǎn)換機(jī)制,就是Spring3.x中新引入的HttpMessageConverter即消息轉(zhuǎn)換器機(jī)制。
#Http請(qǐng)求的抽象 還是回到請(qǐng)求-響應(yīng),也就是解析請(qǐng)求體,然后返回響應(yīng)報(bào)文這個(gè)最基本的Http請(qǐng)求過(guò)程中來(lái)。我們知道,在servlet標(biāo)準(zhǔn)中,可以用javax.servlet.ServletRequest接口中的以下方法:
public ServletInputStream getInputStream() throws IOException;
來(lái)得到一個(gè)ServletInputStream。這個(gè)ServletInputStream中,可以讀取到一個(gè)原始請(qǐng)求報(bào)文的所有內(nèi)容。同樣的,在javax.servlet.ServletResponse接口中,可以用以下方法:
public ServletOutputStream getOutputStream() throws IOException;
來(lái)得到一個(gè)ServletOutputStream,這個(gè)ServletOutputSteam,繼承自java中的OutputStream,可以讓你輸出Http的響應(yīng)報(bào)文內(nèi)容。
讓我們嘗試著像SpringMVC的設(shè)計(jì)者一樣來(lái)思考一下。我們知道,Http請(qǐng)求和響應(yīng)報(bào)文本質(zhì)上都是一串字符串,當(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)文。
我們從流中,只能讀取到原始的字符串報(bào)文,同樣,我們往輸出流中,也只能寫(xiě)原始的字符。而在java世界中,處理業(yè)務(wù)邏輯,都是以一個(gè)個(gè)有業(yè)務(wù)意義的對(duì)象為處理維度的,那么在報(bào)文到達(dá)SpringMVC和從SpringMVC出去,都存在一個(gè)字符串到j(luò)ava對(duì)象的阻抗問(wèn)題。這一過(guò)程,不可能由開(kāi)發(fā)者手工轉(zhuǎn)換。我們知道,在Struts2中,采用了OGNL來(lái)應(yīng)對(duì)這個(gè)問(wèn)題,而在SpringMVC中,它是HttpMessageConverter機(jī)制。我們先來(lái)看兩個(gè)接口。
#HttpInputMessage 這個(gè)類是SpringMVC內(nèi)部對(duì)一次Http請(qǐng)求報(bào)文的抽象,在HttpMessageConverter的read()方法中,有一個(gè)HttpInputMessage的形參,它正是SpringMVC的消息轉(zhuǎn)換器所作用的受體“請(qǐng)求消息”的內(nèi)部抽象,消息轉(zhuǎn)換器從“請(qǐng)求消息”中按照規(guī)則提取消息,轉(zhuǎn)換為方法形參中聲明的對(duì)象。
package org.springframework.http; import java.io.IOException; import java.io.InputStream; public interface HttpInputMessage extends HttpMessage { InputStream getBody() throws IOException; }
#HttpOutputMessage 這個(gè)類是SpringMVC內(nèi)部對(duì)一次Http響應(yīng)報(bào)文的抽象,在HttpMessageConverter的write()方法中,有一個(gè)HttpOutputMessage的形參,它正是SpringMVC的消息轉(zhuǎn)換器所作用的受體“響應(yīng)消息”的內(nèi)部抽象,消息轉(zhuǎn)換器將“響應(yīng)消息”按照一定的規(guī)則寫(xiě)到響應(yīng)報(bào)文中。
package org.springframework.http; import java.io.IOException; import java.io.OutputStream; public interface HttpOutputMessage extends HttpMessage { OutputStream getBody() throws IOException; }
#HttpMessageConverter 對(duì)消息轉(zhuǎn)換器最高層次的接口抽象,描述了一個(gè)消息轉(zhuǎn)換器的一般特征,我們可以從這個(gè)接口中定義的方法,來(lái)領(lǐng)悟Spring3.x的設(shè)計(jì)者對(duì)這一機(jī)制的思考過(guò)程。
package org.springframework.http.converter; import java.io.IOException; import java.util.List; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; public interface HttpMessageConverter<T> { boolean canRead(Class<?> clazz, MediaType mediaType); boolean canWrite(Class<?> clazz, MediaType mediaType); List<MediaType> getSupportedMediaTypes(); T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException; void write(T t, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException; }
HttpMessageConverter接口的定義出現(xiàn)了成對(duì)的canRead(),read()和canWrite(),write()方法,MediaType是對(duì)請(qǐng)求的Media Type屬性的封裝。舉個(gè)例子,當(dāng)我們聲明了下面這個(gè)處理方法。
@RequestMapping(value="/string", method=RequestMethod.POST) public @ResponseBody String 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值寫(xiě)入響應(yīng)報(bào)文,當(dāng)然,此時(shí)canWrite()方法返回true。
我們可以用下面的圖,簡(jiǎn)單描述一下這個(gè)過(guò)程。
#RequestResponseBodyMethodProcessor 將上述過(guò)程集中描述的一個(gè)類是org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor,這個(gè)類同時(shí)實(shí)現(xiàn)了HandlerMethodArgumentResolver和HandlerMethodReturnValueHandler兩個(gè)接口。前者是將請(qǐng)求報(bào)文綁定到處理方法形參的策略接口,后者則是對(duì)處理方法返回值進(jìn)行處理的策略接口。兩個(gè)接口的源碼如下:
package org.springframework.web.method.support; import org.springframework.core.MethodParameter; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; public interface HandlerMethodArgumentResolver { boolean supportsParameter(MethodParameter parameter); Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception; } package org.springframework.web.method.support; import org.springframework.core.MethodParameter; import org.springframework.web.context.request.NativeWebRequest; public interface HandlerMethodReturnValueHandler { boolean supportsReturnType(MethodParameter returnType); void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception; }
RequestResponseBodyMethodProcessor這個(gè)類,同時(shí)充當(dāng)了方法參數(shù)解析和返回值處理兩種角色。我們從它的源碼中,可以找到上面兩個(gè)接口的方法實(shí)現(xiàn)。
對(duì)HandlerMethodArgumentResolver接口的實(shí)現(xiàn):
public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(RequestBody.class); } public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { Object argument = readWithMessageConverters(webRequest, parameter, parameter.getGenericParameterType()); String name = Conventions.getVariableNameForParameter(parameter); WebDataBinder binder = binderFactory.createBinder(webRequest, argument, name); if (argument != null) { validate(binder, parameter); } mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult()); return argument; }
對(duì)HandlerMethodReturnValueHandler接口的實(shí)現(xiàn)
public boolean supportsReturnType(MethodParameter returnType) { return returnType.getMethodAnnotation(ResponseBody.class) != null; } public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException { mavContainer.setRequestHandled(true); if (returnValue != null) { writeWithMessageConverters(returnValue, returnType, webRequest); } }
看完上面的代碼,整個(gè)HttpMessageConverter消息轉(zhuǎn)換的脈絡(luò)已經(jīng)非常清晰。因?yàn)閮蓚€(gè)接口的實(shí)現(xiàn),分別是以是否有@RequestBody和@ResponseBody為條件,然后分別調(diào)用HttpMessageConverter來(lái)進(jìn)行消息的讀寫(xiě)。
如果你想問(wèn),怎么樣跟蹤到RequestResponseBodyMethodProcessor中,請(qǐng)你按照前面幾篇博文的思路,然后到這里spring-mvc-showcase下載源碼回來(lái),對(duì)其中HttpMessageConverter相關(guān)的例子進(jìn)行debug,只要你肯下功夫,相信你一定會(huì)有屬于自己的收獲的。
#思考 張小龍?jiān)谡勎⑿诺谋举|(zhì)時(shí)候說(shuō):“微信只是個(gè)平臺(tái),消息在其中流轉(zhuǎn)”。在我們對(duì)SpringMVC源碼分析的過(guò)程中,我們可以從HttpMessageConverter機(jī)制中領(lǐng)悟到類似的道理。在SpringMVC的設(shè)計(jì)者眼中,一次請(qǐng)求報(bào)文和一次響應(yīng)報(bào)文,分別被抽象為一個(gè)請(qǐng)求消息HttpInputMessage和一個(gè)響應(yīng)消息HttpOutputMessage。
處理請(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)處理各種消息形式。但是,只要這些消息所蘊(yùn)含的“有效信息”是一致的,那么各種不同的消息轉(zhuǎn)換器,都會(huì)生成同樣的轉(zhuǎn)換結(jié)果。至于各種消息間解析細(xì)節(jié)的不同,就被屏蔽在不同的HttpMessageConverter實(shí)現(xiàn)類中了。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot整合RestTemplate用法的實(shí)現(xiàn)
本篇主要介紹了RestTemplate中的GET,POST,PUT,DELETE、文件上傳和文件下載6大常用的功能,具有一定的參考價(jià)值,感興趣的可以了解一下2023-08-08解決JDBC Connection Reset的問(wèn)題分析
這篇文章主要介紹了解決JDBC Connection Reset的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-04-04Java實(shí)現(xiàn)一個(gè)簡(jiǎn)單的文件上傳案例示例代碼
這篇文章主要介紹了Java實(shí)現(xiàn)一個(gè)簡(jiǎn)單的文件上傳案例,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07mybatis之調(diào)用帶輸出參數(shù)的存儲(chǔ)過(guò)程(Oracle)
這篇文章主要介紹了mybatis調(diào)用帶輸出參數(shù)的存儲(chǔ)過(guò)程(Oracle),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11Spring定時(shí)任務(wù)@scheduled多線程使用@Async注解示例
這篇文章主要為大家介紹了Spring定時(shí)任務(wù)@scheduled多線程使用@Async注解示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11