探索SpringMVC-HandlerAdapter之RequestMappingHandlerAdapter-参数解析

前言

上回,我们大概讲了下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的答案

  1. 参数解析器:HandlerMethodArgumentResolver
    Spring抽象出来了参数解析器,用于解析不同的参数。也正是因为有各种不同来源的参数,所以Spring实现了多达31个参数解析器,且默认注册的多达25个参数解析器。
    RequestMappingAdapterHandler默认注册的参数解析器
    图示就是RequestMappingAdapterHandler默认注册的参数解析器,而HandlerMethodArgumentResolverComposite是一个特殊的实现。如果从策略模式的角度看,他就是策略选择器。用于寻找可以解析特定参数的参数解析器,并解析参数。

  2. 底层的支持体系
    我们知道不同的参数解析器,负责不同来源的参数解析。但也因此需要不同的技术手段的支撑。其中,下面的最为重要:

    • 类型转换体系: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写回响应数据

关于类型转换、DataBinder,感兴趣的同学可自行通过官网学习:
3. Validation, Data Binding, and Type Conversion

再回头看类的UML图,我们会发现参数解析器的接口方法中有一个WebDataBinderFactory参数。

  • 为什么需要这个参数?因为某些参数需要通过他来完成属性绑定,同时还需要他的入参校验能力。
  • 为什么是Factory?因为有的参数解析器并不需要WebDataBinder的能力来完成参数解析或者参数校验。例如解析@RequestHeader的Map参数只需要一股脑将所有header封装到Map里面返回即可,没有校验过程。
  • 为什么是作为方法入参,而不是参数解析器的内置属性?因为每个请求的入参对象都是不一样的,如果作为内部属性的话,在执行属性绑定,那得出大问题。

到这里可能有些同学要晕了,一下子WebDataBinder,一下子又是HttpMessageConverter。又说不是所有的参数解析器都需要这些东西。就说咱最常用的两个参数解析器来说吧

参数解析器描述
RequestResponseBodyMethodProcessor从request.body中读取流,并利用HttpMessageConverter转换成目标参数对象。同时还利用WebDataBinder做入参校验
RequestParamMethodArgumentResolver从地址栏获取参数,并利用WebDataBinder做参数类型转换,其底层使用到ConversionService和PropertyEditor。没错,他不支持入参校验。

小结

  1. Spring通过HandlerMethodArgumentResolver进行参数解析。不同的参数解析器,处理不同来源的参数。
  2. 不同的参数解析器依赖的工具不一样。涉及输入输出流的交互时,才需要HttpMessageConverter。而name-value形式的参数解析,例如@HttpHeader、@RequestParam,在从对应的参数来源读取到参数值后,则需要依赖WebDataBinder来实现参数类型转换、属性绑定。

RequestParamMethodArgumentResolver

  • 支持哪些参数
    @RequestParam注解,特殊情况:当Map数据类型时,要求@RequestParam的name属性存在才支持。否则由RequestParamMapMethodArgumentResolver提供支持,用于获取所有的地址栏参数。

  • 怎么解析的
    核心逻辑在他的父类方法AbstractNamedValueMethodArgumentResolver#resolveArgument里面

    1. 将@RequestParam解析到NamedValueInfo(会被缓存)
    2. 从NamedValueInfo获取参数名字
    3. 通过参数名获取参数值
    4. 对参数值进行处理(默认值、是否必须、处理空值)
    5. 通过WebDataBinder进行参数类型转换

    而通过参数名获取参数值,则由对应的实际子类来实现。不用说,对于@RequestParam而言,肯定是RequestParamMethodArgumentResolver从request中获取了。不过,不是简单的request.getParameterValues(paramName),因为参数类型还可能是MultipartFile类型,就需要调用另外的方法获取了。

    此外,值得留意的是,他并不会进行参数校验。

RequestResponseBodyMethodProcessor

  • 支持哪些参数
    不必多说,只要是@RequestBody注解的参数都支持。
  • 怎么解析的
    核心逻辑相对简单:
    1. 通过HttpMessageConverter进行参数的读取转换,无非就是遍历所有的消息转换器去转换。当然,我们的RequestBodyAdvice必然是在这个地方工作的了。而消息转换器最典型的实现json相关的实现了,默认的实现有MappingJackson2HttpMessageConverter、GsonHttpMessageConverter取决于哪个包存在。当然你也可以通过WebMvcConfigurer添加自己的消息转换器。
    2. 通过WebDataBinder进行参数检验。
    3. 如果需要的话,对参数进行包装。针对入参类型:Optional

后记

下次我们就聊开篇谈到的三个问题中的第二个:怎么处理方法调用的返回值。

上一篇:
探索SpringMVC-九大组件之HandlerAdapter
下一篇:
探索SpringMVC-HandlerAdapter之RequestMappingHandlerAdapter-返回值处理
第一篇:
探索SpringMVC-web上下文

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的springmvc-config.xml配置文件的例子: ```xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- 开启注解扫描 --> <context:component-scan base-package="com.example.controller"/> <!-- 配置视图解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/"/> <property name="suffix" value=".jsp"/> </bean> <!-- 配置静态资源处理 --> <mvc:resources mapping="/static/**" location="/static/"/> <!-- 配置RequestMappingHandlerAdapter --> <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"> <property name="messageConverters"> <list> <bean class="org.springframework.http.converter.StringHttpMessageConverter"> <property name="supportedMediaTypes"> <list> <value>text/plain;charset=UTF-8</value> <value>text/html;charset=UTF-8</value> </list> </property> </bean> <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"> <property name="supportedMediaTypes"> <list> <value>application/json;charset=UTF-8</value> </list> </property> </bean> </list> </property> </bean> <!-- 配置RequestMappingHandlerMapping --> <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/> </beans> ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值