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. 項目&相關博文
- 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