上一篇文章Spring与MVC(四),我们分析了DispatcherServlet
在处理请求时是如何找到正确的Controller,以及如何执行。在这篇文章中,我们来看分析一下Spring MVC是如何处理方法参数以及响应返回值的。
首先让我们来看一下Spring MVC中两个重要的接口,这两个接口分别对应请求方法参数的处理、响应返回值的处理,分别是HandlerMethodArgumentResolver
和HandlerMethodReturnValueHandler
。
处理方法参数
请求首先被DispatcherServlet
截获,通过handlerMapping
获得HandlerExecutionChain
,然后调用getHandlerAdapter()
方法获得HandlerAdapter
。通过HandlerAdapter
来调用用户定义的handler,调用流程如下:
- AbstractHandlerMethodAdapter.handle
- RequestMappingHandlerAdapter.handleInternal
- RequestMappingHandlerAdapter.invokeHandlerMethod
RequestMappingHandlerAdapter.invokeHandlerMethod
方法执行用户的方法。其中对于每个请求都会实例化一个ServletInvocableHandlerMethod
,然后调用invokeAndHandle
方法进行处理,它会分别对请求跟响应进行处理:
1 | public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, |
其中InvocableHandlerMethod.invokeForRequest
方法负责解析参数、执行用户方法:
1 | public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer, |
首先通过getMethodArgumentValues
方法获取请求参数,然后调用用户的controller方法,获得返回值returnValue
然后返回:
1 | private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer, |
首先,调用getMethodParameters
获得用户的controller方法匹配到的参数。然后获取参数的解析器:
1 | private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) { |
遍历所有的参数解析器,调用每个解析器的supportsParameter
方法判断它是否能解析parameter。这里有26中参数解析器供选择:
- RequestParamMethodArgumentResolver
- RequestParamMapMethodArgumentResolver
- PathVariableMethodArgumentResolver
- PathVariableMapMethodArgumentResolver
- MatrixVariableMethodArgumentResolver
- MatrixVariableMapMethodArgumentResolver
- ServletModelAttributeMethodProcessor
- RequestResponseBodyMethodProcessor
- RequestPartMethodArgumentResolver
- RequestHeaderMethodArgumentResolver
- RequestHeaderMapMethodArgumentResolver
- ServletCookieValueMethodArgumentResolver
- ExpressionValueMethodArgumentResolver
- SessionAttributeMethodArgumentResolver
- RequestAttributeMethodArgumentResolver
- ServletRequestMethodArgumentResolver
- ServletResponseMethodArgumentResolver
- HttpEntityMethodProcessor
- RedirectAttributesMethodArgumentResolver
- ModelMethodProcessor
- MapMethodProcessor
- ErrorsMethodArgumentResolver
- SessionStatusMethodArgumentResolver
- UriComponentsBuilderMethodArgumentResolver
- RequestParamMethodArgumentResolver
- ServletModelAttributeMethodProcessor
第7个ServletModelAttributeMethodProcessor
和第26个ServletModelAttributeMethodProcessor
区别在于前者的annotationNotRequired
属性为false
,后者的annotationNotRequired
属性为true
。
@RequestParam
看一下被@RequestParam
注释的参数如何被解析。
首先参数解析器选择的是RequestParamMethodArgumentResolver
,看一下它的resolveArgument
方法的主流程是如何工作的:
- 调用
AbstractNamedValueMethodArgumentResolver.getNamedValueInfo
从@RequestParam
注释中获取参数的名称 - 调用
AbstractnamedValueMethodArgumentResolver.resolveStringValue
解析参数名称中可能存在占位符、表达式 - 调用
RequestParamMethodArgumentResolver.resolveName
从request中获取参数的值 - 参数值再经过
WebDataBinder
的转化
RequestParamMethodArgumentResolver.resolveName
RequestParamMethodArgumentResolver.resolveName
的调用流程如下:
- 调用
MultipartResolutionDelegate.resolveMultipartArgument
处理文件上传的情况 - 调用
getNativeRequest
调用
ServletWebRequest.getParameterValues
获取相应名称的参数值ServletWebRequest.getParameterValues
的调用流程:- RequestFacade.getParameterValues
- Request.getParameterValues(String name),根据参数名称获取参数值
参数转化
// TODO
@RequestHeader
看一下被@RequestHeader
注释的参数如何被解析。
首先参数解析器选择的是RequestHeaderMapMethodArgumentResolver
,看一下它的resolveArgument
方法的主流程是如何工作的:
- 如果参数的类型是
MultiValueMap
的子类,遍历请求中的header,将header添加到HttpHeaders
中 - 否则,遍历请求中的header,将header添加到
LinkedHashMap
中
@PathVariable
看一下被@PathVariable
注释的参数如何被解析。
首先参数解析器选择的是PathVariableMethodArgumentResolver
,看一下它的resolveArgument
方法的主流程是如何工作的。
它的流程和RequestParamMethodArgumentResolver
是一致的,因为它们调用的都是AbstractNamedValueMethodArgumentResolver
父类的resolveArgument
方法。唯一不同的是resolveName
方法。
PathVariableMethodArgumentResolver.resolveName
resolveName
方法从request的多个属性中获取org.springframework.web.servlet.HandlerMapping.uriTemplateVariables
属性,然后从中获取参数的值。
那么org.springframework.web.servlet.HandlerMapping.uriTemplateVariables
属性何时获得的呢,其实它是在根据请求获取处理方法的时候获得的,即DispatcherServlet
的doDispatch
方法执行getHandler
的时候。具体是在获得最佳匹配函数之后,处理它的时候:RequestMappingInfoHandlerMapping.handleMatch
:
1 | uriVariables = getPathMatcher().extractUriTemplateVariables(bestPattern, lookupPath); |
extractUriTemplateVariables
函数根据模板(bestPattern)和请求的路径(lookupPath)获得路径中参数与值的映射表。
@RequestBody
看一下被@RequestBody
注释的参数如何被解析。
首先参数解析器选择的是RequestResponseBodyMethodProcessor
,看一下它的resolveArgument
方法的主流程是如何工作的。
- 调用
RequestResponseBodyMethodProcessor.readWithMessageConverters
获取参数数据,它实际调用的是AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters
方法 - 参数值再经过
WebDataBinder
的转化
AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters
AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters
的功能是选用合适的消息转换器(MessageConverter)将数据转换成参数需要的类型,候选的消息转换器有一下10种:
- ByteArrayHttpMessageConverter(支持byte[]类型)
- StringHttpMessageConverter(defaultCharset=”UTF-8”)(支持String类型)
- StringHttpMessageConverter(defaultCharset=”ISO-8859-1”)(支持String类型)
- ResourceHttpMessageConverter(支持Resource类型的子类)
- ResourceRegionHttpMessageConverter
- SourceHttpMessageConverter(支持javax.xml.transform.stax.StAXSource, javax.xml.transform.dom.DOMSource, javax.xml.transform.stream.StreamSource, javax.xml.transform.sax.SAXSource, javax.xml.transform.Source)
- AllEncompassingFormHttpMessageConverter(支持MultiValueMap的子类)
- MappingJackson2HttpMessageConverter(mediaType是
application/json
和application/*+json
其中之一,且参数是Jackson的ObjectMapper可以反序列化的类型。) - MappingJackson2HttpMessageConverter
- Jaxb2RootElementHttpMessageConverter(参数带着@XmlRootElement注释,或者带着@XmlType。并且mediaType是受支持的)
参数转化
// TODO
绑定对象
如果对象没有注释修饰,选择ServletModelAttributeMethodProcessor
解析器(annotationNotRequired属性为true)。
看一下ServletModelAttributeMethodProcessor
的supportsParameter
方法如何工作:
1 | public boolean supportsParameter(MethodParameter parameter) { |
首先判断参数是否有ModelAttribute
注释,没有。this.annotationNotRequired
为true
。再调用BeanUtils.isSimplePropertry
判断参数的类是否是”简单”属性,如果不是”简单”属性则ServletModelAttributeMethodProcessor
支持该参数。”简单”属性指的是:原始类型、String
、CharSequence
、Number
、Date
、URI
、URL
、Locale
、Class
、或者是相应的数组。
1 | public static boolean isSimpleProperty(Class<?> clazz) { |
接着分析ServletModelAttributeMethodProcessor
的resolveArgument
方法的主流程是如何工作的。
- 调用
ModelFactory.getNameForParameter
获取参数的名称,一般是类名称首字母转小写。 - 调用
createAttribute
来生成绑定的对象,注意这时对象的内容为空 - 调用
binderFactory.createBinder
来生成一个数据绑定器。默认情况这里具体的类是ExtendedServletRequestDataBinder
调用
bindRequestParameters
来绑定请求参数,其中ServletModelAttributeMethodProcessor
的bindRequestParameters
方法如下1
2
3
4
5protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;
servletBinder.bind(servletRequest);
}其中servletBinder为
ExtendedServletRequestDataBinder
调用dataBinder的
bind
方法填充对象中的数据1
2
3
4
5
6
7
8
9public void bind(ServletRequest request) {
MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class);
if (multipartRequest != null) {
bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
}
addBindValues(mpvs, request);
doBind(mpvs);
}
首先读取请求中所有参数以及参数对应的值生成`MutablePropertyValues`。然后调用`addBindValues`将请求路径中的参数与值合并到mpvs中。
然后调用`doBind`将请求的参数绑定到对象上。
- 调用
validateIfApplicable
方法,判断生成的参数对象是否符合要求
@RequestBody
如果参数带着@RequestBody
注释,选择RequestResponseBodyMethodProcessor
。来看一下它的resolveArgument
方法是如何工作的:
调用
readWithMessageConverters
读取请求内容生成对应的参数对象其中messageConverters有如下7个供选择:
- ByteArrayHttpMessageConverter
- StringHttpMessageConverter
- ResourceHttpMessageConverter
- SourceHttpMessageConverter
- AllEncompassingFormHttpMessageConverter
- Jaxb2RootElementHttpMessageConverter
MappingJackson2HttpMessageConverter
注意
MappingJackson2HttpMessageConverter
并不是默认有的,需要在配置文件中配置通过调用messageConverter的
canRead
方法判断这个messageConverter是否可以读取请求生成对应的参数。找到合适的messageConverter之后执行三步操作:
调用
RequestBodyAdvice
的beforeBodyRead
方法对请求进行处理。- 调用messageConverter的
read
方法,读取请求内容生成对应的参数对象 调用
RequestBodyAdvice
的afterBodyRead
方法对生成的参数对象进行处理返回生成的参数对象
创建
WebDataBinder
,具体的类是ExtendedServletRequestDataBinder
- 调用
validateIfApplicable
方法,判断生成的参数对象是否符合要求
返回值的响应
回到ServletInvocableHandlerMethod.invokeAndHandle
方法,调用invokeForRequest
方法执行完controller方法得到返回值returnValue
。
然后调用返回值处理器处理返回值,返回值处理器为HandlerMethodReturnValueHandlerComposite
,执行handleReturnValue
:
1 | public void handleReturnValue(Object returnValue, MethodParameter returnType, |
首先调用selectHandler
函数在当前所有可选的处理器中执行supportsReturnType
方法选择支持的处理器。可选返回值处理器默认有15个:
- ModelAndViewMethodReturnValueHandler
- ModelMethodProcessor
- ViewMethodReturnValueHandler
- ResponseBodyEmitterReturnValueHandler
- StreamingResponseBodyReturnValueHandler
- HttpEntityMethodProcessor
- HttpHeadersReturnValueHandler
- CallableMethodReturnValueHandler
- DeferredResultMethodReturnValueHandler
- AsyncTaskMethodReturnValueHandler
- ModelAttributeMethodProcessor
- RequestResponseBodyMethodProcessor
- ViewNameMethodReturnValueHandler
- MapMethodProcessor
- ModelAttributeMethodProcessor
ViewNameMethodReturnValueHandler
返回值为字符串时默认选择ViewNameMethodReturnValueHandler
,我们来看一下它的返回值处理方法handleReturnValue
:
1 | public void handleReturnValue(Object returnValue, MethodParameter returnType, |
我们可以看到它在ModelAndViewContainer
中设置viewName,如果viewName中包含了”redirect:”字符串,表示重定向。
返回到RequestMappingHandlerAdapter.invokeHandlerMethod
方法,执行完invokeAndHandle
之后,再调用getModelAndView
方法返回一个ModelAndView
对象,里面包含view以及model。
返回到DispatcherServlet.doDispatch
方法,获得ModelAndView
对象,最后调用processDispatchResult
渲染页面或者处理异常。
RequestResponseBodyMethodProcessor
如果返回值带着ResponseBody
注释,选择RequestResponseBodyMethodProcessor
,我们来看一下它的返回值处理方法handleReturnValue
:
1 | public void handleReturnValue(Object returnValue, MethodParameter returnType, |
handleReturnValue
方法调用AbstractMessageConverterMethodProcessor.writeWithMessageConverters
方法将返回值处理后输出。它的工作步骤如下:
- 调用
getAcceptableMediaTypes
方法来获取请求头的”ACCEPT”字段指定客户端能够接收的内容类型requestedMediaTypes
调用
getProducibleMedaiTypes
方法获取当前能够生成的内容类型producibleMediaTypes
getProducibleMedaiTypes
遍历当前所有的messageConverters:- ByteArrayHttpMessageConverter
- StringHttpMessageConverter
- ResourceHttpMessageConverter
- SourceHttpMessageConverter
- AllEncompassingFormHttpMessageConverter
Jaxb2RootElementHttpMessageConverter
调用
canWrite
方法判断返回的类是否可以由该converter
来输出,如果可以输出的话添加该converter
支持的MediaType
。
调用
isCompatibleWith
方法,从requestedMediaTypes
和producibleMediaTypes
中挑选兼容的类型- 遍历
messageConverters
,调用canWrite
方法选择合适的messageConverter - 获取
RequestResponseBodyAdviceChain
,调用beforeBodyWrite
对返回值进行处理 - 在返回头上加入”Content-Disposition”
- 调用messageConverter的
write
方法,在返回头上加入contentType。然后调用writeInternal
方法,将转化过后的数据直接写到response中