Spring MVC HTTP Message Conversionhtml
在spring mvc中,一個http請求和響應流程以下圖所示:java
其中 HttpMessageConvert 扮演了 http請求消息和響應消息進行轉換和角色。ios
這裏先說一下對於 HTTP 請求和響應的通常抽象。web
咱們知道,在servlet標準中,能夠用javax.servlet.ServletRequest 接口中的如下方法:spring
public ServletInputStream getInputStream() throws IOException;
來獲得一個ServletInputStream。這個ServletInputStream中,能夠讀取到一個原始請求報文的全部內容。api
一樣的,在javax.servlet.ServletResponse 接口中,能夠用如下方法:mvc
public ServletOutputStream getOutputStream() throws IOException;
來獲得一個ServletOutputStream,這個ServletOutputSteam,繼承自java中的OutputStream,可讓你輸出Http的響應報文內容。app
讓咱們嘗試着像SpringMVC的設計者同樣來思考一下。咱們知道,Http請求和響應報文本質上都是一串字符串,當請求報文來到java世界,它會被封裝成爲一個ServletInputStream的輸入流,供咱們讀取報文。響應報文則是經過一個ServletOutputStream的輸出流,來輸出響應報文。this
而在 SpringMVC 中則提供瞭如下兩個類來進行更高層的對 http 請求和響應的封裝抽象。url
HttpInputMessage
這個類是SpringMVC內部對一次Http請求報文的抽象 ,在HttpMessageConverter的read()方法中,有一個HttpInputMessage的形參,它正是SpringMVC的消息轉換器所做用的受體「請求消息」的內部抽象,消息轉換器從「請求消息」中按照規則提取消息,轉換爲方法形參中聲明的對象。
package org.springframework.http; import java.io.IOException; import java.io.InputStream; public interface HttpInputMessage extends HttpMessage { InputStream getBody() throws IOException; }
HttpOutputMessage
這個類是SpringMVC內部對一次Http響應報文的抽象 ,在HttpMessageConverter的write()方法中,有一個HttpOutputMessage的形參,它正是SpringMVC的消息轉換器所做用的受體「響應消息」的內部抽象,消息轉換器將「響應消息」按照必定的規則寫到響應報文中。
package org.springframework.http; import java.io.IOException; import java.io.OutputStream; public interface HttpOutputMessage extends HttpMessage { OutputStream getBody() throws IOException; }
如今再回來講一下 HttpMessageConvert 在SpringMVC的http請求和響應流程中所起到的做用。
HttpMessageConverter接口描述:
public interface HttpMessageConverter<T> { // Indicate whether the given class and media type can be read by this converter. boolean canRead(Class<?> clazz, MediaType mediaType); // Indicate whether the given class and media type can be written by this converter. boolean canWrite(Class<?> clazz, MediaType mediaType); // Return the list of MediaType objects supported by this converter. List<MediaType> getSupportedMediaTypes(); // Read an object of the given type from the given input message, and returns it. T read(Class<T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException; // Write an given object to the given output message. void write(T t, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException; }
那麼springmvc是如何實例化HttpMessageConvert的呢?這就是<mvc:annotation-driven/>標籤的做用了。
咱們通常要在 spirngmvc的配置文件中加這個標籤,這個標籤的實現類就是
org.springframework.web.servlet.config.AnnotationDrivenBeanDefinitionParser
經過註釋文檔你能夠看到它的主要做用是:
1.注入HandlerMapping(用來處理請求映射的。其中第一個是處理@RequestMapping註解的。第二個會將controller類的名字映射爲請求url。)
* <p>This class registers the following {@link HandlerMapping}s:</p> * <ul> * <li>{@link RequestMappingHandlerMapping} * ordered at 0 for mapping requests to annotated controller methods. * <li>{@link BeanNameUrlHandlerMapping} * ordered at 2 to map URL paths to controller bean names. * </ul>
2.注入HandlerAdapter(RequestMappingHandlerAdapter,HttpRequestHandlerAdapter,SimpleControllerHandlerAdapter三個是用來處理請求的。具體點說就是肯定調用哪一個controller的哪一個方法來處理當前請求。第一個處理@Controller註解的處理器,支持自定義方法參數和返回值(很酷)。第二個是處理繼承HttpRequestHandler的處理器。第三個處理繼承自Controller接口的處理器。)
* <p>This class registers the following {@link HandlerAdapter}s: * <ul> * <li>{@link RequestMappingHandlerAdapter} * for processing requests with annotated controller methods. * <li>{@link HttpRequestHandlerAdapter} * for processing requests with {@link HttpRequestHandler}s. * <li>{@link SimpleControllerHandlerAdapter} * for processing requests with interface-based {@link Controller}s. * </ul>
3.注入HandlerExceptionResolver(處理異常)
* <p>This class registers the following {@link HandlerExceptionResolver}s: * <ul> * <li>{@link ExceptionHandlerExceptionResolver} for handling exceptions * through @{@link ExceptionHandler} methods. * <li>{@link ResponseStatusExceptionResolver} for exceptions annotated * with @{@link ResponseStatus}. * <li>{@link DefaultHandlerExceptionResolver} for resolving known Spring * exception types * </ul>
4.注入AntPathMatcher和UrlPathHelper
* <p>This class registers an {@link org.springframework.util.AntPathMatcher} * and a {@link org.springframework.web.util.UrlPathHelper} to be used by: * <ul> * <li>the {@link RequestMappingHandlerMapping}, * <li>the {@link HandlerMapping} for ViewControllers * <li>and the {@link HandlerMapping} for serving resources * </ul> * Note that those beans can be configured by using the {@code path-matching} MVC namespace element.
更詳細的能夠讀api doc。
好了,那麼HttpMessageConvert又是從哪裏注入的呢?別急,經過讀AnnotationDrivenBeanDefinitionParser類的代碼,發現messageConverters是在注入RequestMappingHandlerAdapter時實例化的,具體的代碼以下,
RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class); handlerAdapterDef.setSource(source); handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); handlerAdapterDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager); handlerAdapterDef.getPropertyValues().add("webBindingInitializer", bindingDef); handlerAdapterDef.getPropertyValues().add("messageConverters", messageConverters); addResponseBodyAdvice(handlerAdapterDef);
handlerAdapterDef.getPropertyValues().add("messageConverters", messageConverters);
咱們就來看看這個標籤默認爲咱們實例化了哪些 HttpMessageConvert。代碼以下,
private ManagedList<?> getMessageConverters(Element element, Object source, ParserContext parserContext) { Element convertersElement = DomUtils.getChildElementByTagName(element, "message-converters"); ManagedList<? super Object> messageConverters = new ManagedList<Object>(); if (convertersElement != null) { messageConverters.setSource(source); for (Element beanElement : DomUtils.getChildElementsByTagName(convertersElement, "bean", "ref")) { Object object = parserContext.getDelegate().parsePropertySubElement(beanElement, null); messageConverters.add(object); } } if (convertersElement == null || Boolean.valueOf(convertersElement.getAttribute("register-defaults"))) { messageConverters.setSource(source); messageConverters.add(createConverterDefinition(ByteArrayHttpMessageConverter.class, source)); RootBeanDefinition stringConverterDef = createConverterDefinition(StringHttpMessageConverter.class, source); stringConverterDef.getPropertyValues().add("writeAcceptCharset", false); messageConverters.add(stringConverterDef); messageConverters.add(createConverterDefinition(ResourceHttpMessageConverter.class, source)); messageConverters.add(createConverterDefinition(SourceHttpMessageConverter.class, source)); messageConverters.add(createConverterDefinition(AllEncompassingFormHttpMessageConverter.class, source)); if (romePresent) { messageConverters.add(createConverterDefinition(AtomFeedHttpMessageConverter.class, source)); messageConverters.add(createConverterDefinition(RssChannelHttpMessageConverter.class, source)); } if (jackson2XmlPresent) { RootBeanDefinition jacksonConverterDef = createConverterDefinition(MappingJackson2XmlHttpMessageConverter.class, source); GenericBeanDefinition jacksonFactoryDef = createObjectMapperFactoryDefinition(source); jacksonFactoryDef.getPropertyValues().add("createXmlMapper", true); jacksonConverterDef.getConstructorArgumentValues().addIndexedArgumentValue(0, jacksonFactoryDef); messageConverters.add(jacksonConverterDef); } else if (jaxb2Present) { messageConverters.add(createConverterDefinition(Jaxb2RootElementHttpMessageConverter.class, source)); } if (jackson2Present) { RootBeanDefinition jacksonConverterDef = createConverterDefinition(MappingJackson2HttpMessageConverter.class, source); GenericBeanDefinition jacksonFactoryDef = createObjectMapperFactoryDefinition(source); jacksonConverterDef.getConstructorArgumentValues().addIndexedArgumentValue(0, jacksonFactoryDef); messageConverters.add(jacksonConverterDef); } else if (gsonPresent) { messageConverters.add(createConverterDefinition(GsonHttpMessageConverter.class, source)); } } return messageConverters; }
經過上面的代碼,能夠很清楚的看到默認實例化的HttpMessageConvert以下:
ByteArrayHttpMessageConverter
StringHttpMessageConverter
ResourceHttpMessageConverter
SourceHttpMessageConverter
AllEncompassingFormHttpMessageConverter
若是相應的消息轉換類庫在classpath下而且可以被加載(classload),那麼如下convert也會默認實例化,
AtomFeedHttpMessageConverter
RssChannelHttpMessageConverter
MappingJackson2XmlHttpMessageConverter
Jaxb2RootElementHttpMessageConverter
MappingJackson2HttpMessageConverter
GsonHttpMessageConverter
哈哈,齊全了!有這些convert轉換消息仍是不夠的。它是如何選擇一個具體的convert來轉換消息的呢?
HttpMessageConverter接口的定義出現了成對的canRead(),read()和canWrite(),write()方法,MediaType是對請求的MediaType屬性的封裝。
舉個例子,當咱們聲明瞭下面這個處理方法。
@RequestMapping(value="/string", method=RequestMethod.POST) public @ResponseBody String readString(@RequestBody String string) { return "Read string '" + string + "'"; }
在SpringMVC進入readString方法前,會根據@RequestBody註解選擇適當的HttpMessageConverter實現類來將請求參數解析到string變量中,具體來講是使用了StringHttpMessageConverter類,它的canRead()方法返回true,而後它的read()方法會從請求中讀出請求參數,綁定到readString()方法的string變量中。
當SpringMVC執行readString方法後,因爲返回值標識了@ResponseBody,SpringMVC將使用StringHttpMessageConverter的write()方法,將結果做爲String值寫入響應報文,固然,此時canWrite()方法返回true。
這個轉換過程就是本文一開始貼的圖片。將上述過程集中描述的一個類是org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor,這個類同時實現了HandlerMethodArgumentResolver和HandlerMethodReturnValueHandler兩個接口。前者是將請求報文綁定處處理方法形參的策略接口,後者則是對處理方法返回值進行處理的策略接口。兩個接口的源碼以下:
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這個類,同時充當了方法參數解析和返回值處理兩種角色。
參考引用:http://my.oschina.net/lichhao/blog/172562
http://my.oschina.net/HeliosFly/blog/205343
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html
===================END===================