SpringMVC源码(六)-@RequestBody和@ResponseBody


在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 responseBody(){
	Map 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两个接口。

SpringMVC源码(四)-请求处理可知,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
    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) {
            // 判断是否支持返回值类型
            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方法完成转换。

至此我们基本走完了一个HTTP请求报文经过处理后到HTTP响应报文的转换过程。现在你可能有个疑惑,SpringMVC我们都是开箱即用,这些参数解析器和返回值处理器在哪里定义的呢?在核心的HandlerAdapter实现类RequestMappingHandlerAdapter的初始化方法中定义的,具体可以去看SpringMVC源码(三)-组件初始化

而在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做到了灵活又便捷的使用方式,其实在内部是做了大量的准备工作的。今天看了一篇文章,也是同样的道理,目前有些在互联网的浪潮中一举成名或者一夜暴富的例子,很多人便会误以为快速成功很容易实现,但却没有看到这些成功的人往往都付出了几年甚至数十年的努力和积累,才能有现在的厚积薄发。希望与所有努力的人的共勉,不骄不躁,不要焦虑!

本文发表于2017年10月18日 22:34
(c)注:本文转载自https://my.oschina.net/u/2377110/blog/1552979,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如有侵权行为,请联系我们,我们会及时删除.

阅读 4509 讨论 1 喜欢 5

日韩化妆品代购 正品保证 假一赔十

刀架在脖子上让发的,走过路过看一下8....

讨论

能不能把代码排个版?

人觉得很赞      回复


周娱

君子和而不同
按照自己的方式,去度过人生

7008 2392298
抢先体验

扫码体验
趣味小程序
文字表情生成器

加入组织

扫码添加周娱微信
备注“加入组织”
邀请进开发群

闪念胶囊

这个世界上,别人只会看你现在的样子而不是以后的样子。你以后的样子只有自己才相信。如果没有执行力,一切都是虚妄。

对普通人来说,人和人相处其实最重要的是感觉。感觉不好,你说什么都没用,怎么解释都没用,越说越错,反正最后不好的锅都往你身上扣。所谓“说你行你就行,不行也行。说你不行,你就不行,行也不行”就是这个意思。狼要吃人根本不需要理由,你也同样叫不醒装睡的人。遇到这种情况,早点闪人才是上策。不过大部分人的问题是没有闪人的心态,能力,和资源。

考985不牛逼,考上才牛逼。创业不牛逼,创业成功才牛逼。这个社会上很多人把目标当成牛逼的资本,牛逼哄哄的,死活不听劝,然后做的一塌糊涂,给别人添麻烦,让别人帮他料理后事,对此只能呵呵。

当你尝到用生气解决问题的甜头后,你就懒得再用其他方式了。你却忽略了,生气是鸩毒啊,剂量用够了,你的关系也玩完了。

年轻的时候你只搞事业不谈恋爱,等你事业有成了,钱相对自由了,你可能已经没有荷尔蒙了。

如果你经常雇佣比你矮小的人,将来我们就会变成矮人国,变成一家侏儒公司。相反,如果你每次都雇用比你高大的人,日后我们必能成为一家巨人公司。

如果一个人有充裕的时间去完成一项工作,那么他就会放慢节奏或者增加其他不必要的工作,直到花光所有的时间。

天空不是人类休息的地方,人类应该去亲近海洋。

一个人的正直程度,取决于他肯为原则付出的牺牲。

Copyright © 2016 - 2018 Cion.
All Rights Reserved.
备案:鲁ICP备16007319号.