[SpringMVC 源码] SpringMVC 中 HTTP 请求与响应原理

来源:互联网 发布:做淘宝直播怎么找商家 编辑:程序博客网 时间:2024/06/04 21:02

一、现象描述

通过 ajax post 方式调用 SpringMVC 接口时,返回 json 对象一般有 ObjectString 两种。而返回对象定义为String 类型时,发现中文乱码。现象截图如下:

中文乱码截图

前端调用代码如下:通过传入的 url 来区分

function fSave(url) {   var obj = {};    obj['cateId'] = $("input[name=cateId]").val();    obj['cateName'] = $("input[name=cateName]").val();    $.ajax({        url: url,        method: 'post',        contentType: 'application/json', // 这句不加出现415错误:Unsupported Media Type        data: JSON.stringify(obj), // 以json字符串方式传递        success: function(data) {            console.log(data);        },        error: function(data) {            console.log("error...");            console.log(data);        }    });}

服务端接口定义如下:

@RequestMapping(value = "/category/save-by-map", method = RequestMethod.POST)@ResponseBodypublic String saveByMap(@RequestBody Map<String, Object> valMap) {    return valMap.toString();}@RequestMapping(value = "/category/save-by-map-2", method = RequestMethod.POST)@ResponseBodypublic Map<String, Object> saveByMap2(@RequestBody Map<String, Object> valMap) {    return valMap;}

二、HTTP 请求与响应原理

SpringMVC 版本 4.1.1.RELEASE

流程图如下:
占坑。。

UML时序图如下:
占坑。。

概述:在 ServletInvocableHandlerMethod 类的 invokeAndHandle() 方法中,SpringMVC 解析请求参数,并调用相应的方法;对方法的返回值进行相应的处理后返回。

invokeAndHandle() 方法

具体细节如下:

1. 解析请求参数并调用相应的接口方法,获得返回值:invokeForRequest()

在该方法中,调用 getMethodArgumentValues() 方法获取方法输入参数,调用 invoke() 方法调用相应的接口方法,最终返回方法返回值。invokeForRequest() 方法截图如下:

invokeForRequest() 方法

1.1 解析请求参数: getMethodArgumentValues()

在该方法中,主要调用HandlerMethodArgumentResolverComposite 类的 resolveArgument 方法,根据输入参数取的对应的 HandlerMethodArgumentResolver策略类(策略模式)解析方法参数:

argumentResolvers.resolveArgument() 方法

调用方法参数处理策略类的 resolveArgument() 方法:

解析输入参数策略类

此处示例输入参数有注解 @RequestBody,因此调用的策略类为:RequestResponseBodyMethodProcessor,对应的还有 @RequestParam 对应的 RequestParamMethodArgumentResolver 策略类等。而在该类的resolveArgument() 方法的实现中,调用了 readWithMessageConverters() 方法:

resolveArgument() 方法

而该方法中,则调用了其父类 AbstractMessageConverterMethodArgumentResolverreadWithMessageConverters() 方法:

readWithMessageConverters() 方法

该方法中,则调用具体的 HttpMessageConverter 类的read() 方法对方法参数进行转换,本示例中由于输入的参数为 Object 类型,因此调用的为 AbstractJackson2HttpMessageConverter 类的 read() 方法:

read() 方法

如输入的参数为 String 类型,则会调用相应的 StringHttpMessageConverter 类:

这里写图片描述

1.2 根据参数调用具体的处理方法,并取得返回值: invoke()

该方法中,主要通过反射机制调用响应的方法并取得返回值:

invoke()

getBridgedMethod() 方法返回值为:

getBridgedMethod() 方法

继续往下走,进入具体的方法调用:

具体方法调用

1.3 返回返回值: return returnValue;

此时的 returnValue 为 Object 类型:

returnValue

2. 调用 returnValueHandlershandleReturnValue() 方法

此处,returnValueHandlersHandlerMethodReturnValueHandlerComposite 类,调用该类的handleReturnValue() 方法,该方法中可判断哪个返回值处理策略类 HandlerMethodReturnValueHandler,然后调用 handleReturnValue() 方法 处理返回值:

handleReturnValue() 方法

2.1 获取具体的返回值处理类: getReturnValueHandler()

该方法根据具体的返回值处理类是否支持方法指定的返回类型(returnType: @ResponseBody, ModelAndView 等) 判断,此处由于返回值为 @ResponseBody,因此调用的策略类为: RequestResponseBodyMethodProcessor(如果定义返回类型为ModelAndView ,则返回值处理器为ModelAndViewMethodReturnValueHandler):

getReturnValueHandler()

2.2 调用具体的方法返回值处理类: handleReturnValue()

RequestResponseBodyMethodProcessor 类的 handleReturnValue() 方法中,调用父类AbstractMessageConverterMethodProcessorwriteWithMessageConverters() 方法,根据返回值类型调用相应的消息转换器 HttpMessageConverter 类,此处与前面 1.1 中,调用 readWithMessageConverters() 方法类似:

这里写图片描述

这里写图片描述

注:方法中的 getProducibleMediaTypes() 方法会获取 @RequestMapping(value = "/save-by-model", method = RequestMethod.POST, produces = "text/plain;charset=utf-8") 指定的 produces 的值:

produces

遍历消息转换器HttpMessageConverter 列表,通过方法 canWrite() 判断该消息转换器是否可操作返回的 Class 类型:

canWrite()

取第一个匹配的消息转换器类,调用该类的 write() 方法:

write()

注:此处调用 write() 方法之前会调用 adviceChain.invoke() 方法对返回值进行处理(AOP 机制)。

此处消息转换器类为 MappingJackson2HttpMessageConverter 类,往下执行,调用父类的 write() 方法,再调用具体消息转换器类的 writeInternal() 方法,此处为调用父类的 AbstractJackson2HttpMessageConverterwriteInternal() 方法:

writeInternal()

再调用具体的 writePrefix(), writeSuffix() 方法,此处设计模式为 模板方法模式MappingJackson2HttpMessageConverter 的相应实现如下:

MappingJackson2HttpMessageConverter 类的实现

三、解决方案


根据第二点的原理解析,可以发现中文乱码出现的原因为:处理返回值时,由于返回值类型为 String, 即调用了 StringHttpMessageConverter 类对返回数据进行转换,而给消息转换器支持的默认字符编码集为 ISO-8859-1:

StringHttpMessageConverter

而返回 Object 类型无中文乱码,是因为该返回值类型调用的消息转换器为 MappingJackson2HttpMessageConverter 而该消息转换器支持的字符编码为 UTF-8,因此无乱码:

这里写图片描述

综上,解决方案为指定具体的返回值, 如:

@RequestMapping(value = "/save-by-map", method = RequestMethod.POST, produces = "text/plain;charset=utf-8")@ResponseBodypublic String saveByMap(@RequestBody Map<String, Object> valMap) {    return valMap.toString();}

或者通过 xml 设置: springmvc @ResponseBody 返回中文乱码

输出为:

正确输出


注:HttpMessageConvertercanRead(), canWrite() 方法根据: 具体的消息转换类是否支持返回值类型以及相应的编码集是否匹配,如常规的消息抓换抽象类中:

canRead()

supports()

而使用 Jackson 的消息转换抽象类中,canRead()方法以输入参数类型转换而来的的 Jackson JavaType是否可被反序列化以及编码集是否匹配,canWrite()方法以返回值类型转换而来的的 Jackson JavaType是否可被序列化以及编码集是否匹配来判断:

AbstractJackson2HttpMessageConverter