title: 190831-SpringBoot系列教程web篇之如何自定义参数解析器

banner: /spring-blog/imgs/190831/logo.jpg

tags:

  • 请求参数

    categories:
  • SpringBoot
  • 高级篇
  • Web

    date: 2019-08-31 16:45:48

    keywords: Spring SpringBoot 参数解析 HandlerMethodArgumentResolver

SpringMVC提供了各种姿势的http参数解析支持,从前面的GET/POST参数解析篇也可以看到,加一个@RequsetParam注解就可以将方法参数与http参数绑定,看到这时自然就会好奇这是怎么做到的,我们能不能自己定义一种参数解析规则呢?

本文将介绍如何实现自定义的参数解析,并让其生效

I. 环境搭建

首先得搭建一个web应用才有可能继续后续的测试,借助SpringBoot搭建一个web应用属于比较简单的活;

创建一个maven项目,pom文件如下

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7</version>
<relativePath/> <!-- lookup parent from update -->
</parent> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
<java.version>1.8</java.version>
</properties> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies> <build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</pluginManagement>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>

II. 自定义参数解析器

对于如何自定义参数解析器,一个较推荐的方法是,先搞清楚springmvc接收到一个请求之后完整的处理链路,然后再来看在什么地方,什么时机,来插入自定义参数解析器,无论是从理解还是实现都会简单很多。遗憾的是,本篇主要目标放在的是使用角度,所以这里只会简单的提一下参数解析的链路,具体的深入留待后续的源码解析

1. 参数解析链路

http请求流程图,来自 SpringBoot是如何解析HTTP参数的

既然是参数解析,所以肯定是在方法调用之前就会被触发,在Spring中,负责将http参数与目标方法参数进行关联的,主要是借助org.springframework.web.method.support.HandlerMethodArgumentResolver类来实现

/**
* Iterate over registered {@link HandlerMethodArgumentResolver}s and invoke the one that supports it.
* @throws IllegalStateException if no suitable {@link HandlerMethodArgumentResolver} is found.
*/
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
if (resolver == null) {
throw new IllegalArgumentException("Unknown parameter type [" + parameter.getParameterType().getName() + "]");
}
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}

上面这段核心代码来自org.springframework.web.method.support.HandlerMethodArgumentResolverComposite#resolveArgument,主要作用就是获取一个合适的HandlerMethodArgumentResolver,实现将http参数(webRequest)映射到目标方法的参数上(parameter)

所以说,实现自定义参数解析器的核心就是实现一个自己的HandlerMethodArgumentResolver

2. HandlerMethodArgumentResolver

实现一个自定义的参数解析器,首先得有个目标,我们在get参数解析篇里面,当时遇到了一个问题,当传参为数组时,定义的方法参数需要为数组,而不能是List,否则无法正常解析;现在我们则希望能实现这样一个参数解析,以支持上面的场景

为了实现上面这个小目标,我们可以如下操作

a. 自定义注解ListParam

定义这个注解,主要就是用于表明,带有这个注解的参数,希望可以使用我们自定义的参数解析器来解析;

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ListParam {
/**
* Alias for {@link #name}.
*/
@AliasFor("name") String value() default ""; /**
* The name of the request parameter to bind to.
*
* @since 4.2
*/
@AliasFor("value") String name() default "";
}

b. 参数解析器ListHandlerMethodArgumentResolver

接下来就是自定义的参数解析器了,需要实现接口HandlerMethodArgumentResolver

public class ListHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(ListParam.class);
} @Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
ListParam param = parameter.getParameterAnnotation(ListParam.class);
if (param == null) {
throw new IllegalArgumentException(
"Unknown parameter type [" + parameter.getParameterType().getName() + "]");
} String name = "".equalsIgnoreCase(param.name()) ? param.value() : param.name();
if ("".equalsIgnoreCase(name)) {
name = parameter.getParameter().getName();
}
String ans = webRequest.getParameter(name);
if (ans == null) {
return null;
} String[] cells = StringUtils.split(ans, ",");
return Arrays.asList(cells);
}
}

上面有两个方法:

  • supportsParameter就是用来表明这个参数解析器适不适用

    • 实现也比较简单,就是看参数上有没有前面定义的ListParam注解
  • resolveArgument 这个方法就是实现将http参数粗转换为目标方法参数的具体逻辑
    • 上面主要是为了演示自定义参数解析器的过程,实现比较简单,默认只支持List<String>

3. 注册

上面虽然实现了自定义的参数解析器,但是我们需要把它注册到HandlerMethodArgumentResolver才能生效,一个简单的方法如下

@SpringBootApplication
public class Application extends WebMvcConfigurationSupport { @Override
protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new ListHandlerMethodArgumentResolver());
} public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}

4. 测试

为了验证我们的自定义参数解析器ok,我们开两个对比的rest服务

@RestController
@RequestMapping(path = "get")
public class ParamGetRest {
/**
* 自定义参数解析器
*
* @param names
* @param age
* @return
*/
@GetMapping(path = "self")
public String selfParam(@ListParam(name = "names") List<String> names, Integer age) {
return names + " | age=" + age;
} @GetMapping(path = "self2")
public String selfParam2(List<String> names, Integer age) {
return names + " | age=" + age;
}
}

演示demo如下,添加了ListParam注解的可以正常解析,没有添加注解的会抛异常

II. 其他

0. 项目&相关博文

1. 一灰灰Blog

尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

SpringBoot系列教程web篇之如何自定义参数解析器的更多相关文章

  1. SpringBoot系列教程web篇之Post请求参数解析姿势汇总

    作为一个常年提供各种Http接口的后端而言,如何获取请求参数可以说是一项基本技能了,本篇为<190824-SpringBoot系列教程web篇之Get请求参数解析姿势汇总>之后的第二篇,对 ...

  2. SpringBoot系列教程web篇之Get请求参数解析姿势汇总

    一般在开发web应用的时候,如果提供http接口,最常见的http请求方式为GET/POST,我们知道这两种请求方式的一个显著区别是GET请求的参数在url中,而post请求可以不在url中:那么一个 ...

  3. SpringBoot系列教程web篇之自定义异常处理HandlerExceptionResolver

    关于Web应用的全局异常处理,上一篇介绍了ControllerAdvice结合@ExceptionHandler的方式来实现web应用的全局异常管理: 本篇博文则带来另外一种并不常见的使用方式,通过实 ...

  4. SpringBoot系列教程web篇之过滤器Filter使用指南扩展篇

    前面一篇博文介绍了在 SpringBoot 中使用 Filter 的两种使用方式,这里介绍另外一种直接将 Filter 当做 Spring 的 Bean 来使用的方式,并且在这种使用方式下,Filte ...

  5. SpringBoot系列教程Web篇之开启GZIP数据压缩

    本篇可以归纳在性能调优篇,虽然内容非常简单,但效果可能出乎预料的好: 分享一个真实案例,我们的服务部署在海外,国内访问时访问服务时,响应有点夸张:某些返回数据比较大的接口,耗时在 600ms+上,然而 ...

  6. SpringBoot系列教程web篇Listener四种注册姿势

    java web三要素Filter, Servlet前面分别进行了介绍,接下来我们看一下Listener的相关知识点,本篇博文主要内容为SpringBoot环境下,如何自定义Listener并注册到s ...

  7. SpringBoot系列教程web篇Servlet 注册的四种姿势

    原文: 191122-SpringBoot系列教程web篇Servlet 注册的四种姿势 前面介绍了 java web 三要素中 filter 的使用指南与常见的易错事项,接下来我们来看一下 Serv ...

  8. SpringBoot系列教程web篇之过滤器Filter使用指南

    web三大组件之一Filter,可以说是很多小伙伴学习java web时最早接触的知识点了,然而学得早不代表就用得多.基本上,如果不是让你从0到1写一个web应用(或者说即便从0到1写一个web应用) ...

  9. SpringBoot系列教程web篇之全局异常处理

    当我们的后端应用出现异常时,通常会将异常状况包装之后再返回给调用方或者前端,在实际的项目中,不可能对每一个地方都做好异常处理,再优雅的代码也可能抛出异常,那么在 Spring 项目中,可以怎样优雅的处 ...

随机推荐

  1. SparkSQL读写外部数据源--数据分区

    import com.twq.dataset.Utils._ import org.apache.spark.sql.{SaveMode, SparkSession} object FileParti ...

  2. 自定义express中间件

    const http = require('http') class LikeExpress { constructor() { this.middleList = [] this.routes = ...

  3. iptables的使用

    四表五链 四表(table):raw.mangle.nat.filter 五链(chain):PREROUTING.INPUT.FORWARD.OUTPUT.POSTROUTING 每个表存在几个或全 ...

  4. 指数基金介绍专栏(4):上证50AH优选指数

    作者:牛大 | 公众号:定投五分钟 大家好,我是牛大.每天五分钟,投资你自己:坚持基金定投,终会财富自由! 想必大家会有疑问,什么是上证50AH优选指数?今天老师给大家答疑解惑,详细介绍一下上证50A ...

  5. [译]深度神经网络的多任务学习概览(An Overview of Multi-task Learning in Deep Neural Networks)

    译自:http://sebastianruder.com/multi-task/ 1. 前言 在机器学习中,我们通常关心优化某一特定指标,不管这个指标是一个标准值,还是企业KPI.为了达到这个目标,我 ...

  6. 转载:线性回归建模–变量选择和正则化(1):R包glmnet

    2013-07-15 21:41:04   #本文的目的在于介绍回归建模时变量选择和正则化所用的R包,如glmnet,ridge,lars等.算法的细节尽量给文献,这个坑太大,hold不住啊. 1.变 ...

  7. Spark-Streaming DirectKafka count 案例

    Spark-Streaming DirectKafka count 统计跟直接 kafka 统计类似,只不过这里使用的是 Direct 的方式,Direct方式使用的 kafka 低级API,不同的地 ...

  8. 【AtCoder】 ARC 102

    link C-Triangular Relationship 发现要么全部是\(K\)的倍数,要么全部是模\(K\)余\(K/2,(K=2n)\) #include<bits/stdc++.h& ...

  9. 「ZJOI2019」开关

    传送门 Description 有一些一开始全都是关的开关,每次随机选择一个(每个开关概率不同)开关并改变它的状态,问达到目标状态的期望步数 Solution  \(P=\sum_{i=1}^{n}p ...

  10. 咕泡学院java架构vip课程

    1.wps文档地址 https://docs.qq.com/doc/DRVNLUndvTmFSdEhO 2.百度网盘地址 https://pan.baidu.com/s/1uxaTzJZHKrsw_H ...