【WEB系列】如何自定义参数解析器

文章目录
  1. I. 环境搭建
  2. II. 自定义参数解析器
    1. 1. 参数解析链路
    2. 2. HandlerMethodArgumentResolver
      1. a. 自定义注解ListParam
      2. b. 参数解析器ListHandlerMethodArgumentResolver
    3. 3. 注册
    4. 4. 测试
  3. II. 其他
    1. 0. 项目&相关博文
    2. 1. 一灰灰Blog

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

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

I. 环境搭建

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

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

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
<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类来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@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

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
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才能生效,一个简单的方法如下

1
2
3
4
5
6
7
8
9
10
11
12
@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服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@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或者有更好的建议,欢迎批评指正,不吝感激

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

一灰灰blog


打赏 如果觉得我的文章对您有帮助,请随意打赏。
分享到