前言
上回,我们大概讲了下HandlerAdapter。今天带大家来认识一下,我们最常用的RequestMappingHandlerAdapter。不过只能给大家先开个头,讲下参数解析。
RequestMappingHandlerAdapter
在介绍HandlerAdapter时,我们就知道HandlerAdapter屏蔽了DispatcherServlet屏蔽了Handler的调用细节,也知道了@RequestMapping的中间桥梁是HandlerMethod。而这意味着RequestMappingHandlerMappping是负责调用HandlerMethod处理请求逻辑的。于是,我们很自然想到几个重要的问题:
- 怎么获取方法调用的入参
- 怎么处理方法调用的返回值
- 怎么处理方法抛出的异常
深入分析入参解析需求
@RequestMapping的参数来源
常见的几个相信很多同学都是知道:@RequestParam、@RequestBody、@PathVariable、@RequestHeader
还有些可能不太常用的:@SessiontAttribuite、@CookieValue、@MatrixVariable
HttpServletRequest、HttpServletResponse、HttpSession、Locale
这里就不再一一列举了,感兴趣的同学可以通过小标题的链接到官网看看。
- 问题一:
从这里可以看到,我们的处理器方法入参五花八门、来源不一而足。因此如果要解析参数,就必须知道这些参数来自于哪里。 - 问题二:
在了解问题来自于哪里之后,我们面临的另外一个问题是:参数类型转换。举个例子,@RequestParam的参数来自于地址栏参数,无疑地址栏参数是字符类型。但是如果我们的方法参数是个Date类型,怎么办?又例如,@RequestBody是我们自定义类型,而获取参数体通常是一个Stream。
Spring的答案
-
参数解析器:HandlerMethodArgumentResolver
Spring抽象出来了参数解析器,用于解析不同的参数。也正是因为有各种不同来源的参数,所以Spring实现了多达31个参数解析器,且默认注册的多达25个参数解析器。
图示就是RequestMappingAdapterHandler默认注册的参数解析器,而HandlerMethodArgumentResolverComposite是一个特殊的实现。如果从策略模式的角度看,他就是策略选择器。用于寻找可以解析特定参数的参数解析器,并解析参数。 -
底层的支持体系
我们知道不同的参数解析器,负责不同来源的参数解析。但也因此需要不同的技术手段的支撑。其中,下面的两种最为重要:-
类型转换体系:ConversionService
类型转换服务是spring-core中提供的,用于类型转换。而在SpringMVC中,只要是name-value这种类型的参数,则都需要依赖他来进行参数转换。例如,上面提到@RequestParam,将String转为Date。
注意,他背后是一个体系,涉及到一系列的相关接口,例如:Converter<S,T>。这里不拓展了。 -
对象属性编辑体系:ConfigurablePropertyAccessor
ConfigurablePropertyAccessor是spring-beans提供的,用于修改对象属性。最典型的实现就是BeanWrapperImpl。同时他还可以在修改属性值之前进行参数转换。而这个参数类型转换能力,来自于两部分,一个是上面说的ConversionService。另一个是PropertyEditor,通常是我们自定义的。因为大部分的类型转换ConversionService都能做。最后有PropertyHandler通过反射将属性值设置进去。 -
对象属性绑定: DataBinder
DataBinder来自于spring-context,主要是利用上述的对象编辑体系来完成属性设置。而其子类WebDataBinder来自于spring-web,利用request作为属性值的来源完成请求入参的属性设置。除此之外,他还能做入参校验。我们的@Valid/@Validated就是他处理的。 -
Http消息转换器:HttpMessageConverter
Http消息转换器是spring-web的内容,主要有两个作用:作用 将Request中的body参数反序列为目标类型的对象 将需要返回的响应值序列化为二进制流,通过Response写回响应数据
-
再回头看类的UML图,我们会发现参数解析器的接口方法中有一个WebDataBinderFactory参数。
- 为什么需要这个参数?因为某些参数需要通过他来完成属性绑定,同时还需要他的入参校验能力。
- 为什么是Factory?因为有的参数解析器并不需要WebDataBinder的能力来完成参数解析或者参数校验。例如解析@RequestHeader的Map参数只需要一股脑将所有header封装到Map里面返回即可,没有校验过程。
- 为什么是作为方法入参,而不是参数解析器的内置属性?因为每个请求的入参对象都是不一样的,如果作为内部属性的话,在执行属性绑定,那得出大问题。
到这里可能有些同学要晕了,一下子WebDataBinder,一下子又是HttpMessageConverter。又说不是所有的参数解析器都需要这些东西。就说咱最常用的两个参数解析器来说吧
参数解析器 | 描述 |
---|---|
RequestResponseBodyMethodProcessor | 从request.body中读取流,并利用HttpMessageConverter转换成目标参数对象。同时还利用WebDataBinder做入参校验 |
RequestParamMethodArgumentResolver | 从地址栏获取参数,并利用WebDataBinder做参数类型转换,其底层使用到ConversionService和PropertyEditor。没错,他不支持入参校验。 |
小结
- Spring通过HandlerMethodArgumentResolver进行参数解析。不同的参数解析器,处理不同来源的参数。
- 不同的参数解析器依赖的工具不一样。涉及输入输出流的交互时,才需要HttpMessageConverter。而name-value形式的参数解析,例如@HttpHeader、@RequestParam,在从对应的参数来源读取到参数值后,则需要依赖WebDataBinder来实现参数类型转换、属性绑定。
RequestParamMethodArgumentResolver
-
支持哪些参数
@RequestParam注解,特殊情况:当Map数据类型时,要求@RequestParam的name属性存在才支持。否则由RequestParamMapMethodArgumentResolver
提供支持,用于获取所有的地址栏参数。 -
怎么解析的
核心逻辑在他的父类方法AbstractNamedValueMethodArgumentResolver#resolveArgument
里面- 将@RequestParam解析到NamedValueInfo(会被缓存)
- 从NamedValueInfo获取参数名字
- 通过参数名获取参数值
- 对参数值进行处理(默认值、是否必须、处理空值)
- 通过WebDataBinder进行参数类型转换
而通过参数名获取参数值,则由对应的实际子类来实现。不用说,对于@RequestParam而言,肯定是RequestParamMethodArgumentResolver从request中获取了。不过,不是简单的request.getParameterValues(paramName),因为参数类型还可能是MultipartFile类型,就需要调用另外的方法获取了。
此外,值得留意的是,他并不会进行参数校验。
RequestResponseBodyMethodProcessor
- 支持哪些参数
不必多说,只要是@RequestBody注解的参数都支持。 - 怎么解析的
核心逻辑相对简单:- 通过HttpMessageConverter进行参数的读取转换,无非就是遍历所有的消息转换器去转换。当然,我们的RequestBodyAdvice必然是在这个地方工作的了。而消息转换器最典型的实现json相关的实现了,默认的实现有MappingJackson2HttpMessageConverter、GsonHttpMessageConverter取决于哪个包存在。当然你也可以通过WebMvcConfigurer添加自己的消息转换器。
- 通过WebDataBinder进行参数检验。
- 如果需要的话,对参数进行包装。针对入参类型:Optional
后记
下次我们就聊开篇谈到的三个问题中的第二个:怎么处理方法调用的返回值。
上一篇:
探索SpringMVC-九大组件之HandlerAdapter
第一篇:
探索SpringMVC-web上下文