上一篇文章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方法获取当前能够生成的内容类型producibleMediaTypesgetProducibleMedaiTypes遍历当前所有的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中