Spring MVC原始碼(三) ----- @RequestBody和@ResponseBody原理解析

chen_hao發表於2019-07-17

概述

在SpringMVC的使用時,往往會用到@RequestBody和@ResponseBody兩個註解,尤其是處理ajax請求必然要使用@ResponseBody註解。這兩個註解對應著Controller方法的引數解析和返回值處理,開始時都是隻知其用,不知原理。我們來看個例子。

@RequestMapping("/requestBody")
public void requestBody(@RequestBody String body, Writer writer) throws IOException{
    writer.write(body);
}

@RequestMapping(value="/responseBody", produces="application/json")
@ResponseBody
public Map<String, Object> responseBody(){
    Map<String, Object> retMap = new HashMap<>();
    retMap.put("param1", "abc");
    return retMap;
}

第一個requestBody請求,使用@RequestBody將HTTP請求體轉換成String型別,第二個responseBody請求,將Map物件轉換成json格式輸出到HTTP響應中。這兩個請求方法沒有什麼特殊,就是一個在引數前加了@RequestBody註解,一個在方法上加了@ResponseBody註解。而這兩個註解是怎麼完成HTTP報文資訊同Controller方法中物件的轉換的呢?

SpringMVC處理請求和響應時,支援多種型別的請求引數和返回型別,而此種功能的實現就需要對HTTP訊息體和引數及返回值進行轉換,為此SpringMVC提供了大量的轉換類,所有轉換類都實現了HttpMessageConverter介面。

public interface HttpMessageConverter<T> {

    // 當前轉換器是否能將HTTP報文轉換為物件型別
    boolean canRead(Class<?> clazz, MediaType mediaType);

    // 當前轉換器是否能將物件型別轉換為HTTP報文
    boolean canWrite(Class<?> clazz, MediaType mediaType);

    // 轉換器能支援的HTTP媒體型別
    List<MediaType> getSupportedMediaTypes();

    // 轉換HTTP報文為特定型別
    T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
            throws IOException, HttpMessageNotReadableException;

    // 將特定型別物件轉換為HTTP報文
    void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException;

}

HttpMessageConverter介面定義了5個方法,用於將HTTP請求報文轉換為java物件,以及將java物件轉換為HTTP響應報文。

對應到SpringMVC的Controller方法,read方法即是讀取HTTP請求轉換為引數物件,write方法即是將返回值物件轉換為HTTP響應報文。SpringMVC定義了兩個介面來操作這兩個過程:引數解析器HandlerMethodArgumentResolver和返回值處理器HandlerMethodReturnValueHandler。

// 引數解析器介面
public interface HandlerMethodArgumentResolver {

    // 解析器是否支援方法引數
    boolean supportsParameter(MethodParameter parameter);

    // 解析HTTP報文中對應的方法引數
    Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;

}

// 返回值處理器介面
public interface HandlerMethodReturnValueHandler {

    // 處理器是否支援返回值型別
    boolean supportsReturnType(MethodParameter returnType);

    // 將返回值解析為HTTP響應報文
    void handleReturnValue(Object returnValue, MethodParameter returnType,
            ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;

}

引數解析器和返回值處理器在底層處理時,都是通過HttpMessageConverter進行轉換。流程如下:

 

 SpringMVC為@RequestBody和@ResponseBody兩個註解實現了統一處理類RequestResponseBodyMethodProcessor,實現了HandlerMethodArgumentResolver和HandlerMethodReturnValueHandler兩個介面。

 由上一篇文章我們可以知道,Controller方法被封裝成ServletInvocableHandlerMethod類,並且由invokeAndHandle方法完成請求處理。

 

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
        Object... providedArgs) throws Exception {

    // 執行請求
    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    
    // 返回值處理
    try {
        this.returnValueHandlers.handleReturnValue(
                returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
    }
    catch (Exception ex) {
        if (logger.isTraceEnabled()) {
            logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
        }
        throw ex;
    }
}

public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,
        Object... providedArgs) throws Exception {
    // 引數解析
    Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
    // invoke Controller方法
    Object returnValue = doInvoke(args);
    return returnValue;
}

在invoke Controller方法的前後分別執行了方法引數的解析和返回值的處理,我們分別來看。

引數解析

private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer,
        Object... providedArgs) throws Exception {

    MethodParameter[] parameters = getMethodParameters();
    Object[] args = new Object[parameters.length];

    // 遍歷所有引數,逐個解析
    for (int i = 0; i < parameters.length; i++) {
        MethodParameter parameter = parameters[i];
        parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
        args[i] = resolveProvidedArgument(parameter, providedArgs);
        if (args[i] != null) {
            continue;
        }

        // 引數解析器解析HTTP報文到引數
        if (this.argumentResolvers.supportsParameter(parameter)) {
            args[i] = this.argumentResolvers.resolveArgument(
                    parameter, mavContainer, request, this.dataBinderFactory);
            continue;
        }
    }
    return args;
}

getMethodArgumentValues方法中的argumentResolvers就是多個HandlerMethodArgumentResolver的集合體,supportsParameter方法尋找引數合適的解析器,resolveArgument呼叫具體解析器的resolveArgument方法執行。

我們從RequestResponseBodyMethodProcessor看看@RequestBody的解析過程。RequestResponseBodyMethodProcessor的supportsParameter定義了它支援的引數型別,即必須有@RequestBody註解。

public boolean supportsParameter(MethodParameter parameter) {
    return parameter.hasParameterAnnotation(RequestBody.class);
}

再來看resolveArgument方法

public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
        NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

    parameter = parameter.nestedIfOptional();
    // 通過HttpMessageConverter讀取HTTP報文
    Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
    String name = Conventions.getVariableNameForParameter(parameter);

    WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
    if (arg != null) {
        validateIfApplicable(binder, parameter);
        if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
            throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
        }
    }
    mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());

    return adaptArgumentIfNecessary(arg, parameter);
}

具體實現由HttpMessageConverter來完成

protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
        Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

    ....

    try {
        inputMessage = new EmptyBodyCheckingHttpInputMessage(inputMessage);

        for (HttpMessageConverter<?> converter : this.messageConverters) {
            Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
            ....
            // 判斷轉換器是否支援引數型別
            if (converter.canRead(targetClass, contentType)) {
                if (inputMessage.getBody() != null) {
                    inputMessage = getAdvice().beforeBodyRead(inputMessage, parameter, targetType, converterType);
                    // read方法執行HTTP報文到引數的轉換
                    body = ((HttpMessageConverter<T>) converter).read(targetClass, inputMessage);
                    body = getAdvice().afterBodyRead(body, inputMessage, parameter, targetType, converterType);
                }
                else {
                    body = getAdvice().handleEmptyBody(null, inputMessage, parameter, targetType, converterType);
                }
                break;
            }
            ...
        }
    }
    catch (IOException ex) {
        throw new HttpMessageNotReadableException("I/O error while reading input message", ex);
    }

    ....

    return body;
}

程式碼部分省略了,關鍵部分即是遍歷所有的HttpMessageConverter,通過canRead方法判斷轉換器是否支援對引數的轉換,然後執行read方法完成轉換。

返回值處理

完成Controller方法的呼叫後,在ServletInvocableHandlerMethod的invokeAndHandle中,使用返回值處理器對返回值進行轉換。

this.returnValueHandlers.handleReturnValue(
                returnValue, getReturnValueType(returnValue), mavContainer, webRequest);

這裡的returnValueHandlers也是HandlerMethodReturnValueHandler的集合體HandlerMethodReturnValueHandlerComposite

public void handleReturnValue(Object returnValue, MethodParameter returnType,
        ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

    // 選擇合適的HandlerMethodReturnValueHandler,如果沒有用@ResposeBody註解和用了註解其返回值處理器肯定不同
    HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
    if (handler == null) {
        throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
    }
    // 執行返回值處理
    handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}

selectHandler方法遍歷所有HandlerMethodReturnValueHandler,呼叫其supportsReturnType方法選擇合適的HandlerMethodReturnValueHandler,然後呼叫其handleReturnValue方法完成處理。

這裡還是以RequestResponseBodyMethodProcessor來分析下@ResponseBody的處理,它的具體實現在AbstractMessageConverterMethodProcessor抽象基類中。

public boolean supportsReturnType(MethodParameter returnType) {
    return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
            returnType.hasMethodAnnotation(ResponseBody.class));
}

RequestResponseBodyMethodProcessor要求方法上有@ResponseBody註解或者方法所在的Controller類上有@ResponseBody的註解。這就是常常用@RestController註解代替@Controller註解的原因,因為@RestController註解自帶@ResponseBody。

handleReturnValue方法實際也是呼叫HttpMessageConverter來完成轉換處理

public void handleReturnValue(Object returnValue, MethodParameter returnType,
        ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
        throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

    mavContainer.setRequestHandled(true);
    ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
    ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

    // 呼叫HttpMessageConverter執行
    writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}

protected <T> void writeWithMessageConverters(T value, MethodParameter returnType,
        ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
        throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

    ....

    if (selectedMediaType != null) {
        selectedMediaType = selectedMediaType.removeQualityValue();
        for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
            // 判斷是否支援返回值型別,返回值型別很有可能不同,如String,Map,List等
            if (messageConverter.canWrite(valueType, selectedMediaType)) {
                outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
                        (Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
                        inputMessage, outputMessage);
                if (outputValue != null) {
                    addContentDispositionHeader(inputMessage, outputMessage);
                    // 執行返回值轉換
                    ((HttpMessageConverter) messageConverter).write(outputValue, selectedMediaType, outputMessage);
                    ...
                }
                return;
            }
        }
    }
    ....
}

使用canWrite方法選擇合適的HttpMessageConverter,然後呼叫write方法完成轉換。

 我們看看傳入的引數 ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

protected ServletServerHttpResponse createOutputMessage(NativeWebRequest webRequest) {
    //獲取HttpServletResponse
    HttpServletResponse response = (HttpServletResponse)webRequest.getNativeResponse(HttpServletResponse.class);
    Assert.state(response != null, "No HttpServletResponse");
    return new ServletServerHttpResponse(response);
}

public class ServletServerHttpResponse implements ServerHttpResponse {
    private final HttpServletResponse servletResponse;
    private final HttpHeaders headers;
    private boolean headersWritten = false;
    private boolean bodyUsed = false;

    public ServletServerHttpResponse(HttpServletResponse servletResponse) {
        Assert.notNull(servletResponse, "HttpServletResponse must not be null");
        //將獲取的HttpServletResponse作為ServletServerHttpResponse的屬性值
        this.servletResponse = servletResponse;
        this.headers = new ServletServerHttpResponse.ServletResponseHttpHeaders();
    }
}

public interface ServletResponse {
    String getCharacterEncoding();

    String getContentType();
    
    //ServletResponse有一個輸出流物件,儲存需要相應客戶端的位元組流
    ServletOutputStream getOutputStream() throws IOException;

    PrintWriter getWriter() throws IOException;

    void setCharacterEncoding(String var1);

    void setContentLength(int var1);

    void setContentLengthLong(long var1);

    void setContentType(String var1);

    void setBufferSize(int var1);

    int getBufferSize();

    void flushBuffer() throws IOException;

    void resetBuffer();

    boolean isCommitted();

    void reset();

    void setLocale(Locale var1);

    Locale getLocale();
}

我們具體看看  ((HttpMessageConverter) messageConverter).write(outputValue, selectedMediaType, outputMessage);

 

protected void writeInternal(Object obj, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
    HttpHeaders headers = outputMessage.getHeaders();
    //建立一個陣列位元組流緩衝物件
    ByteArrayOutputStream outnew = new ByteArrayOutputStream();
    //將obj物件轉換成JSON並寫入ByteArrayOutputStream中
    int len = JSON.writeJSONString(outnew, this.fastJsonConfig.getCharset(), obj, this.fastJsonConfig.getSerializeConfig(), this.fastJsonConfig.getSerializeFilters(), this.fastJsonConfig.getDateFormat(), JSON.DEFAULT_GENERATE_FEATURE, this.fastJsonConfig.getSerializerFeatures());
    headers.setContentLength((long)len);
    //獲取ServletResponse的輸出流物件    
    OutputStream out = outputMessage.getBody();
    //將轉換後的outnew寫入ServletResponse的輸出流物件,這樣就可以給客戶端響應資料了
    outnew.writeTo(out);
    outnew.close();
}

public OutputStream getBody() throws IOException {
    this.bodyUsed = true;
    this.writeHeaders();
    //獲取ServletResponse的輸出流物件    
    //ServletOutputStream getOutputStream() throws IOException;
    return this.servletResponse.getOutputStream();
}

最後我們看看JSON是怎麼將obj物件轉換成JSON物件的流

就是做一些迴圈拼接。

 至此我們基本走完了一個HTTP請求報文經過處理後到HTTP響應報文的轉換過程。現在你可能有個疑惑,SpringMVC我們都是開箱即用,這些引數解析器和返回值處理器在哪裡定義的呢?在核心的HandlerAdapter實現類RequestMappingHandlerAdapter的初始化方法中定義的。

而在RequestMappingHandlerAdapter構造時,也同時初始化了眾多的HttpMessageConverter,以支援多樣的轉換需求。

WebMvcConfigurationSupport.java

protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
    StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
    stringConverter.setWriteAcceptCharset(false);

    messageConverters.add(new ByteArrayHttpMessageConverter());
    messageConverters.add(stringConverter);
    messageConverters.add(new ResourceHttpMessageConverter());
    messageConverters.add(new SourceHttpMessageConverter<Source>());
    messageConverters.add(new AllEncompassingFormHttpMessageConverter());

    if (romePresent) {
        messageConverters.add(new AtomFeedHttpMessageConverter());
        messageConverters.add(new RssChannelHttpMessageConverter());
    }

    if (jackson2XmlPresent) {
        ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.xml().applicationContext(this.applicationContext).build();
        messageConverters.add(new MappingJackson2XmlHttpMessageConverter(objectMapper));
    }
    else if (jaxb2Present) {
        messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
    }

    if (jackson2Present) {
        ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().applicationContext(this.applicationContext).build();
        messageConverters.add(new MappingJackson2HttpMessageConverter(objectMapper));
    }
    else if (gsonPresent) {
        messageConverters.add(new GsonHttpMessageConverter());
    }
}

對於json或xml的轉換方式,只要引入了jackson的依賴,即可自動發現,並註冊相關的轉換器。

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.0</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
    <version>2.9.0</version>
</dependency>

現在明白了SpringMVC做到了靈活又便捷的使用方式,其實在內部是做了大量的準備工作的。

 

相關文章