Spring MVC HTTP Message Conversion

Spring MVC HTTP Message Conversionhtml

在spring mvc中,一個http請求和響應流程以下圖所示:java

其中 HttpMessageConvert 扮演了 http請求消息和響應消息進行轉換和角色。ios

這裏先說一下對於 HTTP 請求和響應的通常抽象。web

 

HTTP 請求和響應的封裝抽象

咱們知道,在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請求和響應流程中所起到的做用。

 

HttpMessageConvert-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===================

相關文章
相關標籤/搜索