随心所欲,自定义参数解析器绑定数据。

题图:from Zoommy

干货

  1. SpringMVC解析器用于解析request请求参数并绑定数据到Controller的入参上。
  2. 自定义一个参数解析器需要实现HandlerMethodArgumentResolver接口,重写supportsParameterresolveArgument方法,配置文件中加入resolver配置。
  3. 如果需要多个解析器同时生效需要在一个解析器中对其他解析器做兼容

缘起

为什么要自定义一个解析器呢?

源于需要对前端请求参数进行手动URLDecode,也即除了Web容器自动decode一次,代码内还需要再decode一次。

针对这种需求,首先想到的是filter或者interceptor实现,但是由于HttpServletRequest对象本身是不提供setParameter()方法的,因此想要修改request中的参数值为decode后的值是不易达到的。

SpringMVC的HandlerMethodArgumentResolver,解析器;其功能就是解析request请求参数并绑定数据到Controller的入参上。因此自定义解析器加入URLDecode逻辑即可完全满足需求。

下面,就一步一步的完成一个解析器由简到繁的实现过程。

实现一个极其简单的参数解析器

具体如何自定义一个参数解析器呢?

其实很简单,一句话——实现HandlerMethodArgumentResolver接口,重写supportsParameterresolveArgument方法,配置文件中加入resolver配置。

示例代码如下:

  • 自定义解析器实现

    1. 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
    1. public class MyArgumentsResolver implements HandlerMethodArgumentResolver {
      /**
      * 解析器是否支持当前参数
      */
      @Override
      public boolean supportsParameter(MethodParameter parameter) {
      // 指定参数如果被应用MyParam注解,则使用该解析器。
      // 如果直接返回true,则代表将此解析器用于所有参数
      return parameter.hasParameterAnnotation(MyParam.class);
      }
    2.  
    3. /**
      * 将request中的请求参数解析到当前Controller参数上
      * @param parameter 需要被解析的Controller参数,此参数必须首先传给{@link #supportsParameter}并返回true
      * @param mavContainer 当前request的ModelAndViewContainer
      * @param webRequest 当前request
      * @param binderFactory 生成{@link WebDataBinder}实例的工厂
      * @return 解析后的Controller参数
      */
      @Override
      public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
      NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
    4.  
    5. return null;
      }
      }
  • 自定义注解

    1. 1
      2
      3
      4
      5
    1. @Target({ElementType.PARAMETER})
      @Retention(RetentionPolicy.RUNTIME)
      @Documented
      public @interface MyParam {
      }
  • 在springmvc配置文件中注册解析器

    1. 1
      2
      3
      4
      5
      6
    1. <mvc:annotation-driven>
      <!--MyArgumentsResolver-->
      <mvc:argument-resolvers>
      <bean class="xxx.xxx.xxx.MyArgumentsResolver"/>
      </mvc:argument-resolvers>
      </mvc:annotation-driven>

好了,现在解析器会把所有应用了@MyParam注解的参数都赋值为null

实现一个解析原始类型的参数解析器

对于如何解析原始类型参数,SpringMVC已经有了一个内置的实现——RequestParamMethodArgumentResolver,因此完全可以参考这个实现来自定义我们自己的解析器。

如上所述,解析器逻辑的主要部分都在resolveArgument方法内,这里就说说自定义该方法的实现。

  1. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
  1. @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
    NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
  2.  
  3. // 解析器中的自定义逻辑——urldecode
    Object arg = URLDecoder.decode(webRequest.getParameter(parameter.getParameterName()), "UTF-8");
  4.  
  5. // 将解析后的值绑定到对应的Controller参数上,利用DataBinder提供的方法便捷的实现类型转换
    if (binderFactory != null) {
  6.  
  7. // 生成参数绑定器,第一个参数为request请求对象,第二个参数为需要绑定的目标对象,第三个参数为需要绑定的目标对象名
    WebDataBinder binder = binderFactory.createBinder(webRequest, null, parameter.getParameterName());
  8.  
  9. try {
  10.  
  11. // 将参数转到预期类型,第一个参数为解析后的值,第二个参数为绑定Controller参数的类型,第三个参数为绑定的Controller参数
    arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
  12.  
  13. } catch (ConversionNotSupportedException ex) {
    throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
    parameter.getParameterName(), parameter, ex.getCause());
    } catch (TypeMismatchException ex) {
    throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
    parameter.getParameterName(), parameter, ex.getCause());
    }
    }
    return arg;
    }

添加解析对象类型参数的功能

对于如何解析对象类型参数,SpringMVC内也有了一个内置的实现——ModelAttributeMethodProcessor,我们也是参考这个实现来自定义我们自己的解析器。

同样,resolveArgument方法示例如下

  1. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
  1. @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
    NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
  2.  
  3. String name = parameter.getParameterName();
  4.  
  5. // 查找是否已有名为name的参数值的映射,如果没有则创建一个
    Object attribute = mavContainer.containsAttribute(name)
    ? mavContainer.getModel().get(name)
    : this.createAttribute(name, parameter, binderFactory, webRequest);
  6.  
  7. if (binderFactory != null) {
    WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
  8.  
  9. if (binder.getTarget() != null) {
    // 进行参数绑定
    this.bindRequestParameters(binder, webRequest);
    }
  10.  
  11. // 将参数转到预期类型,第一个参数为解析后的值,第二个参数为绑定Controller参数的类型,第三个参数为绑定的Controller参数
    attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
    }
  12.  
  13. return attribute;
    }
  14.  
  15. protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) throws UnsupportedEncodingException {
    // 将key-value封装为map,传给bind方法进行参数值绑定
    Map<String, String> map = new HashMap<>();
    Map<String, String[]> params = request.getParameterMap();
  16.  
  17. for (Map.Entry<String, String[]> entry : params.entrySet()) {
    String name = entry.getKey();
    // 执行urldecode
    String value = URLDecoder.decode(entry.getValue()[0], "UTF-8");
    map.put(name, value);
    }
  18.  
  19. PropertyValues propertyValues = new MutablePropertyValues(map);
  20.  
  21. // 将K-V绑定到binder.target属性上
    binder.bind(propertyValues);
    }

同时支持多个参数解析器生效

到目前为止,不论对于原始类型或者对象类型的参数,我们都可以自定义一个参数解析器了,但是还有一个很严重的问题存在——无法让自定义解析器和现有解析器同时生效。

举个例子,public String myController(@Valid @MyParam param, BindingResult result){},这个方法在执行时是会报错的。他会提示类似如下报错:

An Errors/BindingResult argument is expected to be declared immediately after the model attribute, the @RequestBody or the @RequestPart arguments

是SpringMVC不支持同时使用两个解析器吗?public String myController(@Valid @ModelAttribute param, BindingResult result){},也是两个内置解析器,没有任何问题。

再去看ModelAttributeMethodProcessor的实现,原来是对@Valid做了兼容处理。

因此, 如果需要多个解析器同时生效需要在一个解析器中对其他解析器做兼容。

这里仅以对@Valid进行兼容处理为例,在解析对象类型的解析器实现中进行修改

  1. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
  1. @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
    NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
  2.  
  3. String name = parameter.getParameterName();
  4.  
  5. // 查找是否已有名为name的参数值的映射,如果没有则创建一个
    Object attribute = mavContainer.containsAttribute(name)
    ? mavContainer.getModel().get(name)
    : this.createAttribute(name, parameter, binderFactory, webRequest);
  6.  
  7. if (binderFactory != null) {
    WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
  8.  
  9. if (binder.getTarget() != null) {
    // 进行参数绑定,此方法实现不再赘述,可到上节查看
    this.bindRequestParameters(binder, webRequest);
  10.  
  11. // -----------------------------------对@Valid做兼容----------------------------------------------------
  12.  
  13. // 如果使用了validation校验, 则进行相应校验
    if (parameter.hasParameterAnnotation(Valid.class)) {
    // 如果有校验报错,会将结果放在binder.bindingResult属性中
    binder.validate();
    }
  14.  
  15. // 如果参数中不包含BindingResult参数,直接抛出异常
    if (binder.getBindingResult().hasErrors() && this.isBindExceptionRequired(binder, parameter)) {
    throw new BindException(binder.getBindingResult());
    }
    }
  16.  
  17. // 关键,使Controller中接下来的BindingResult参数可以接收异常
    Map bindingResultModel = binder.getBindingResult().getModel();
    mavContainer.removeAttributes(bindingResultModel);
    mavContainer.addAllAttributes(bindingResultModel);
  18.  
  19. // -----------------------------------对@Valid做兼容----------------------------------------------------
  20.  
  21. // 将参数转到预期类型,第一个参数为解析后的值,第二个参数为绑定Controller参数的类型,第三个参数为绑定的Controller参数
    attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
    }
  22.  
  23. return attribute;
    }
  24.  
  25. /**
    * 检查参数中是否包含BindingResult参数
    */
    protected boolean isBindExceptionRequired(WebDataBinder binder, MethodParameter methodParam) {
    int i = methodParam.getParameterIndex();
    Class[] paramTypes = methodParam.getMethod().getParameterTypes();
    boolean hasBindingResult = paramTypes.length > i + 1 && Errors.class.isAssignableFrom(paramTypes[i + 1]);
    return !hasBindingResult;
    }

OK,到这里,我们自定义的解析器已经可以算是一个完善的参数解析器了,如果有对其他解析器做兼容的需要,只要参照此类方法稍作修改即可。

后记

还记得这次自定义解析器的原因吗——需要对前端请求参数进行手动URLDecode,也即除了Web容器自动decode一次,代码内还需要再decode一次。

事实证明,根本不需要进行二次decode,写出的解析器也就无疾而终了,仅存这篇整理,算是对SpringMVC解析器的一次学习总结吧。

http://coderec.cn/2016/08/27/%E4%B8%80%E6%AD%A5%E4%B8%80%E6%AD%A5%E8%87%AA%E5%AE%9A%E4%B9%89SpringMVC%E5%8F%82%E6%95%B0%E8%A7%A3%E6%9E%90%E5%99%A8/

一步一步自定义SpringMVC参数解析器的更多相关文章

  1. 自定义springmvc参数解析器

    实现spring HandlerMethodArgumentResolver接口 通过使用@JsonArg自定义注解来解析json数据(通过fastjson的jsonPath),支持多个参数(@Req ...

  2. 自定义HandlerMethodArgumentResolver参数解析器和源码分析

    在初学springmvc框架时,我就一直有一个疑问,为什么controller方法上竟然可以放这么多的参数,而且都能得到想要的对象,比如HttpServletRequest或HttpServletRe ...

  3. 实现自定义的参数解析器——HandlerMethodArgumentResolver

    1.为什么需要自己实现参数解析器 我们都知道在有注解的接口方法中加上@RequestBody等注解,springMVC会自动的将消息体等地方的里面参数解析映射到请求的方法参数中. 如果我们想要的信息不 ...

  4. Spring boot中自定义Json参数解析器

    转载请注明出处... 一.介绍 用过springMVC/spring boot的都清楚,在controller层接受参数,常用的都是两种接受方式,如下 /** * 请求路径 http://127.0. ...

  5. SpringMVC自动封装List对象 —— 自定义参数解析器

    前台传递的参数为集合对象时,后台Controller希望用一个List集合接收数据. 原生SpringMVC是不支持,Controller参数定义为List类型时,接收参数会报如下错误: org.sp ...

  6. springmvc 源码分析(三) -- 自定义处理器映射器和自定义处理器适配器,以及自定义参数解析器 和错误跳转自定页面

    测试环境搭建: 本次搭建是基于springboot来实现的,代码在码云的链接:https://gitee.com/yangxioahui/thymeleaf.git DispatcherServlet ...

  7. SpringMVC 自定义参数解析器.

    一.简述 有没有想过像 @RequestParam.@RequestBody 这些注解的工作原理呢?为什么 form 表单.application/json 的参数能够直接封装进 Bean 对象中呢? ...

  8. SpringBoot系列教程web篇之如何自定义参数解析器

    title: 190831-SpringBoot系列教程web篇之如何自定义参数解析器 banner: /spring-blog/imgs/190831/logo.jpg tags: 请求参数 cat ...

  9. springMVC源码分析--RequestParamMethodArgumentResolver参数解析器(三)

    之前两篇博客springMVC源码分析--HandlerMethodArgumentResolver参数解析器(一)和springMVC源码解析--HandlerMethodArgumentResol ...

随机推荐

  1. Shell变量之自定义变量、环境变量

    1:环境变量        环境变量可以帮我们达到很多功能-包括家目录的变换啊.提示字符的显示啊.运行文件搜寻的路径啊等等的那么,既然环境变量有那么多的功能,问一下,目前我的 shell 环境中, 有 ...

  2. gson使用详解

    昨天读一篇文章,看到gson这个词,一开始还以为作者写错了,问了度娘之后才发现是我才疏学浅,于是大概了解了一下gson用法,总体来说还是很简单的. Gson.jar下载 JavaBean转json / ...

  3. 解锁Scott过程中出现的问题及解决办法

    一.conn sys/sys as sysdba; //以DBA的身份登录 出现以下错误 经查 协议适配器错误的问题的原因有三个 监听服务没有起起来.windows平台个一如下操作:开始---程序-- ...

  4. IT技能栈

    C++.JAVA.Objective-C 基本数据类型,集合类如字符串数组字典,自定义数据对象 内存布局,编译运行期的变化 语言特性 输入输出流,文件流,序列化 多线程,并发控制,线程池,锁 网络编程 ...

  5. java内存模型及分块

    转自:http://www.cnblogs.com/BangQ/p/4045954.html 1.JMM简介 2.堆和栈 3.本机内存 4.防止内存泄漏   1.JMM简介   i.内存模型概述 Ja ...

  6. [网络] C# NetHelper网络通信编程类教程与源码下载

    点击下载 NetHelper.zip 主要功能如下所示 检查设置的IP地址是否正确,返回正确的IP地址 检查设置的端口号是否正确,返回正确的端口号 将字符串形式的IP地址转换成IPAddress对象 ...

  7. js跨浏览器事件对象、事件处理程序

    项目中有时候会不用jquery这么好用的框架,需要自己封装一些事件对象和事件处理程序,像封装AJAX那样:这里面考虑最多的还是浏览器的兼容问题,原生js封装如下:var EventUtil={ //节 ...

  8. UITableView编写可以添加,删除,移动的物品栏(二)

    MyTableViewCell.h文件(自定义ViewCell)的内容: MyTableViewCell.m的内容

  9. Sublime Text 3配置LiveReload实现实时刷新

    今天看到一款很强大的插件,LiveReload,实时刷新,也就是说写完html/css/js等不用再到浏览器里按F5啦,在Ctrl+S时浏览器会自动刷新,是不是想想都很爽... Chrome:(据说支 ...

  10. spring源码_下载以及转入eclipse (2016-11-08)

    本例spring源码版本是4.3.0的, 所以jdk需要准备1.8的(不同版本源码要求的jdk不一样) 1.8版本myeclipse10无编译环境,只有运行环境,出现点问题,下载最新版本的Eclips ...