共计 5432 个字符,预计需要花费 14 分钟才能阅读完成。
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 参数绑定,看到这时自然就会好奇这是怎么做到的, 我们能不能自己定义一种参数解析规则呢?
本文将介绍如何实现自定义的参数解析,并让其生效
<!– more –>
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. 项目 & 相关博文
- 190824-SpringBoot 系列教程 web 篇之 Get 请求参数解析姿势汇总
- 190828-SpringBoot 系列教程 web 篇之 Post 请求参数解析姿势汇总
- 工程:https://github.com/liuyueyi/spring-boot-demo
- 项目: https://github.com/liuyueyi/spring-boot-demo/blob/master/spring-boot/202-web-params
1. 一灰灰 Blog
尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现 bug 或者有更好的建议,欢迎批评指正,不吝感激
下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛
- 一灰灰 Blog 个人博客 https://blog.hhui.top
- 一灰灰 Blog-Spring 专题博客 http://spring.hhui.top