SpringMVC: HttpMessageConverter消息轉換器機制


一、概述

在SpringMVC中,可使用@RequestBody和@ResponseBody兩個註解,分別完成請求報文到對象和對象到響應報文的轉 換,底層這種靈活的消息轉換機制,就是Spring3.x中新引入的HttpMessageConverter即消息轉換器機制。java

二、Http請求的抽象

仍是回到請求-響應,也就是解析請求體,而後返回響應報文這個最基本的Http請求過程當中來。咱們知道,在servlet標準中,能夠用javax.servlet.ServletRequest接口中的如下方法:web

public ServletInputStream getInputStream() throws IOException;

來獲得一個ServletInputStream。這個ServletInputStream中,能夠讀取到一個原始請求報文的全部內容。一樣的,在javax.servlet.ServletResponse接口中,能夠用如下方法:spring

public ServletOutputStream getOutputStream() throws IOException;

來獲得一個ServletOutputStream,這個ServletOutputSteam,繼承自java中的OutputStream,可讓你輸出Http的響應報文內容。json

讓咱們嘗試着像SpringMVC的設計者同樣來思考一下。咱們知道,Http請求和響應報文本質上都是一串字符串,當請求報文來到java世界, 它會被封裝成爲一個ServletInputStream的輸入流,供咱們讀取報文。響應報文則是經過一個ServletOutputStream的輸出 流,來輸出響應報文。mvc

咱們從流中,只能讀取到原始的字符串報文,一樣,咱們往輸出流中,也只能寫原始的字符。而在java世界中,處理業務邏輯,都是以一個個有業務意義的對象爲 處理維度的,那麼在報文到達SpringMVC和從SpringMVC出去,都存在一個字符串到java對象的阻抗問題。這一過程,不可能由開發者手工轉 換。咱們知道,在Struts2中,採用了OGNL來應對這個問題,而在SpringMVC中,它是HttpMessageConverter機制。咱們 先來看兩個接口。app

2.一、HttpInputMessage

這個類是SpringMVC內部對一次Http請求報文的抽象,在HttpMessageConverter的read()方法中,有一個 HttpInputMessage的形參,它正是SpringMVC的消息轉換器所做用的受體「請求消息」的內部抽象,消息轉換器從「請求消息」中按照規 則提取消息,轉換爲方法形參中聲明的對象。spa

package org.springframework.http;import java.io.IOException;import java.io.InputStream;public interface HttpInputMessage extends HttpMessage {    InputStream getBody() throws IOException;

}

2.二、HttpOutputMessage

這個類是SpringMVC內部對一次Http響應報文的抽象,在HttpMessageConverter的write()方法中,有一個 HttpOutputMessage的形參,它正是SpringMVC的消息轉換器所做用的受體「響應消息」的內部抽象,消息轉換器將「響應消息」按照一 定的規則寫到響應報文中。.net

package org.springframework.http;import java.io.IOException;import java.io.OutputStream;public interface HttpOutputMessage extends HttpMessage {    OutputStream getBody() throws IOException;

}

2.三、HttpMessageConverter

對消息轉換器最高層次的接口抽象,描述了一個消息轉換器的通常特徵,咱們能夠從這個接口中定義的方法,來領悟Spring3.x的設計者對這一機制的思考過程。設計

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接口的定義出現了成對的canRead(),read()和canWrite(),write()方法,MediaType是對請求的Media Type屬性的封裝。舉個例子,當咱們聲明瞭下面這個處理方法。code

@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。

咱們能夠用下面的圖,簡單描述一下這個過程。

消息轉換圖

2.四、RequestResponseBodyMethodProcessor

將上述過程集中描述的一個類是 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這個類,同時充當了方法參數解析和返回值處理兩種角色。咱們從它的源碼中,能夠找到上面兩個接口的方法實現。

對HandlerMethodArgumentResolver接口的實現:

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;
}

對HandlerMethodReturnValueHandler接口的實現

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);
    }
}

看完上面的代碼,整個HttpMessageConverter消息轉換的脈絡已經很是清晰。由於兩個接口的實現,分別是以是否有@RequestBody和@ResponseBody爲條件,而後分別調用HttpMessageConverter來進行消息的讀寫。

三、<mvc:annotation-driven/>初始化7個轉換器

  • ByteArrayHttpMessageConverter 

  • StringHttpMessageConverter 

  • ResourceHttpMessageConverter 

  • SourceHttpMessageConverter 

  • XmlAwareFormHttpMessageConverter 

  • Jaxb2RootElementHttpMessageConverter 

  • MappingJacksonHttpMessageConverter

對於json的解析就是經過MappingJacksonHttpMessageConverter轉換器完成的。

只添加<mvc:annotation-driven />還不行,須要在classpath環境中能找到Jackson包

 <mvc:annotation-driven>  
        <mvc:message-converters register-defaults="true">  
            <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"             p:supportedMediaTypes="/" />  
        </mvc:message-converters>
        <!--注入的message-converters優先級高於默認注入的json轉換器-->
    </mvc:annotation-driven>

json依賴包

<dependency>
  <groupId>org.codehaus.jackson</groupId>
  <artifactId>jackson-core-asl</artifactId>
  <version>1.9.13</version>
</dependency>
<dependency>
  <groupId>org.codehaus.jackson</groupId>
  <artifactId>jackson-mapper-asl</artifactId>
  <version>1.9.13</version>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.4.4</version>
</dependency>
相關文章
相關標籤/搜索