第二十一讲-不同的参数解析器
第二十一讲-不同的参数解析器
前面我们在介绍HandlerAdapter的时候,它在调用控制器方法之前,就要为控制器方法准备好每一个参数,准备参数的工作,当然是交给参数解析器来完成的。本讲我们来了解一下常见的参数解析器,以及这些参数解析器的作用是什么。
前面我们在介绍RequestMappingHandlerAdapter中见到了Spring默认提供的一些参数解析器,如下面所示:
/*
常见的参数处理器如下:
org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@abbc908
org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver@44afefd5
org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver@9a7a808
org.springframework.web.servlet.mvc.method.annotation.PathVariableMapMethodArgumentResolver@72209d93
org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMethodArgumentResolver@2687f956
org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMapMethodArgumentResolver@1ded7b14
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@29be7749
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor@5f84abe8
org.springframework.web.servlet.mvc.method.annotation.RequestPartMethodArgumentResolver@4650a407
org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver@30135202
org.springframework.web.method.annotation.RequestHeaderMapMethodArgumentResolver@6a4d7f76
org.springframework.web.servlet.mvc.method.annotation.ServletCookieValueMethodArgumentResolver@10ec523c
org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver@53dfacba
org.springframework.web.servlet.mvc.method.annotation.SessionAttributeMethodArgumentResolver@79767781
org.springframework.web.servlet.mvc.method.annotation.RequestAttributeMethodArgumentResolver@78411116
org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver@aced190
org.springframework.web.servlet.mvc.method.annotation.ServletResponseMethodArgumentResolver@245a060f
org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor@6edaa77a
org.springframework.web.servlet.mvc.method.annotation.RedirectAttributesMethodArgumentResolver@1e63d216
org.springframework.web.method.annotation.ModelMethodProcessor@62ddd21b
org.springframework.web.method.annotation.MapMethodProcessor@16c3ca31
org.springframework.web.method.annotation.ErrorsMethodArgumentResolver@2d195ee4
org.springframework.web.method.annotation.SessionStatusMethodArgumentResolver@2d6aca33
org.springframework.web.servlet.mvc.method.annotation.UriComponentsBuilderMethodArgumentResolver@21ab988f
org.springframework.web.servlet.mvc.method.annotation.PrincipalMethodArgumentResolver@29314cc9
org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@4e38d975
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@35f8a9d3
*/
我们发现,有这么多的参数解析器,但是我们并不会一一的介绍,而是挑一些重要的,我们在开发中常用的参数解析器介绍学习!
1. 准备工作
我们首先做一些准备,首先编写一个控制器类,并准备一些控制器方法,我们为不同的方法参数上加上不同的注解和类型等。
static class Controller {
public void test(
@RequestParam("name1") String name1, // name1=张三
String name2, // name2=李四
@RequestParam("age") int age, // age=18,涉及到类型转换问题,由String转为int(请求路由上默认都是String类型)
@RequestParam(name = "home", defaultValue = "${JAVA_HOME}") String home1, // spring 获取非请求中的数据
@RequestParam("file") MultipartFile file, // 上传文件
@PathVariable("id") int id, // /test/124 /test/{id}, restful风格路由的参数
@RequestHeader("Content-Type") String header, // 获取请求头中的参数
@CookieValue("token") String token, // 获取cookie值
@Value("${JAVA_HOME}") String home2, // spring 获取数据 ${} #{}
HttpServletRequest request, // request, response, session ...(特殊类型的参数)
@ModelAttribute("abc") User user1, // name=zhang&age=18(要和User对象中的属性对应上)
User user2, // name=zhang&age=18
@RequestBody User user3 // json,获取请求体中的数据
) {
}
static class User {
private String name;
private int age;
// 省略getter和setter方法
}
}
接下来我们编写测试代码:
// 配置类
@Configuration
public class WebConfig {
}
- 测试的请求对象:
// 测试的请求对象:
private static HttpServletRequest mockRequest() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setParameter("name1", "zhangsan");
request.setParameter("name2", "lisi");
request.addPart(new MockPart("file", "abc", "hello".getBytes(StandardCharsets.UTF_8)));
Map<String, String> map = new AntPathMatcher().extractUriTemplateVariables("/test/{id}", "/test/123");
System.out.println(map);
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, map);
request.setContentType("application/json");
request.setCookies(new Cookie("token", "123456"));
request.setParameter("name", "张三");
request.setParameter("age", "18");
request.setContent("""
{
"name":"李四",
"age":20
}
""".getBytes(StandardCharsets.UTF_8));
return new StandardServletMultipartResolver().resolveMultipart(request);
}
- 我们将自定义的控制器中的方法封装为HandlerMethod。
HandlerMethod handlerMethod =
new HandlerMethod(new Controller(), Controller.class.getDeclaredMethod("test", String.class, String.class, int.class, String.class, MultipartFile.class, int.class, String.class,
String.class, String.class, HttpServletRequest.class, User.class, User.class, User.class))
-
准备一个对象绑定与类型转换的工具,主要用于字符串到其它类型的转换,这里暂时不加入。
-
准备一个
ModelAndViewContainer
来存储中间Model的结果
ModelAndViewContainer container = new ModelAndViewContainer();
- 最后我们测试这些参数解析器,当然,在测试这些参数解析器之前,我们把这些参数解析器遍历出来:
for (MethodParameter parameter : handlerMethod.getMethodParameters()) {
String annotations = Arrays.stream(parameter.getParameterAnnotations()).map(a -> a.annotationType().getSimpleName()).collect(Collectors.joining());// 获取参数 上的注解
parameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer()); // 设置参数名解析器
System.out.println("["+parameter.getParameterIndex()+"]"+parameter.getParameterType().getSimpleName()+" "+parameter.getParameterName()+" @"+annotations);
}
[0]String name1 @RequestParam
[1]String name2 @
[2]int age @RequestParam
[3]String home1 @RequestParam
[4]MultipartFile file @RequestParam
[5]int id @PathVariable
[6]String header @RequestHeader
[7]String token @CookieValue
[8]String home2 @Value
[9]HttpServletRequest request @
[10]User user1 @ModelAttribute
[11]User user2 @
[12]User user3 @RequestBody
2. @RequestParam参数解析器
解析@RequestParam注解的参数解析器是由RequestParamMethodArgumentResolver
解析器完成的。
我们首先添加一个支持解析@RequestMapping的解析器对象RequestParamMethodArgumentResolver
package com.cherry.a21;
/*
目标: 解析控制器方法的参数值
常见的参数处理器如下:
org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@abbc908
org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver@44afefd5
org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver@9a7a808
org.springframework.web.servlet.mvc.method.annotation.PathVariableMapMethodArgumentResolver@72209d93
org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMethodArgumentResolver@2687f956
org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMapMethodArgumentResolver@1ded7b14
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@29be7749
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor@5f84abe8
org.springframework.web.servlet.mvc.method.annotation.RequestPartMethodArgumentResolver@4650a407
org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver@30135202
org.springframework.web.method.annotation.RequestHeaderMapMethodArgumentResolver@6a4d7f76
org.springframework.web.servlet.mvc.method.annotation.ServletCookieValueMethodArgumentResolver@10ec523c
org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver@53dfacba
org.springframework.web.servlet.mvc.method.annotation.SessionAttributeMethodArgumentResolver@79767781
org.springframework.web.servlet.mvc.method.annotation.RequestAttributeMethodArgumentResolver@78411116
org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver@aced190
org.springframework.web.servlet.mvc.method.annotation.ServletResponseMethodArgumentResolver@245a060f
org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor@6edaa77a
org.springframework.web.servlet.mvc.method.annotation.RedirectAttributesMethodArgumentResolver@1e63d216
org.springframework.web.method.annotation.ModelMethodProcessor@62ddd21b
org.springframework.web.method.annotation.MapMethodProcessor@16c3ca31
org.springframework.web.method.annotation.ErrorsMethodArgumentResolver@2d195ee4
org.springframework.web.method.annotation.SessionStatusMethodArgumentResolver@2d6aca33
org.springframework.web.servlet.mvc.method.annotation.UriComponentsBuilderMethodArgumentResolver@21ab988f
org.springframework.web.servlet.mvc.method.annotation.PrincipalMethodArgumentResolver@29314cc9
org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@4e38d975
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@35f8a9d3
*/
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockPart;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Map;
import java.util.stream.Collectors;
public class A21 {
private static final Logger log = LoggerFactory.getLogger(A21.class);
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(WebConfig.class);
// 准备测试的Request对象
HttpServletRequest multiRequest = mockRequest();
HandlerMethod handlerMethod = new HandlerMethod(new Controller(), Controller.class.getDeclaredMethod("test", String.class, String.class, int.class, String.class, MultipartFile.class, int.class, String.class, String.class, String.class, HttpServletRequest.class, User.class, User.class, User.class));
ModelAndViewContainer container = new ModelAndViewContainer();
for (MethodParameter parameter : handlerMethod.getMethodParameters()) { // false表示必须带有@RequestParam注解,true表示可以省略该注解
RequestParamMethodArgumentResolver resolver = new RequestParamMethodArgumentResolver(null, false);
String annotations = Arrays.stream(parameter.getParameterAnnotations()).map(a -> a.annotationType().getSimpleName()).collect(Collectors.joining());// 获取参数 上的注解
parameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer()); // 设置参数名解析器
// 判断是否支持此参数的处理
if (resolver.supportsParameter(parameter)) {
// 解析并返回参数值
Object result = resolver.resolveArgument(parameter, container, new ServletWebRequest(multiRequest), null);
System.out.println("["+parameter.getParameterIndex()+"]"+parameter.getParameterType().getSimpleName()+" "+parameter.getParameterName()+" @"+annotations+"--->"+result);
} else{
System.out.println("["+parameter.getParameterIndex()+"]"+parameter.getParameterType().getSimpleName()+" "+parameter.getParameterName()+" @"+annotations);
}
}
}
}
运行结果如下:
[0]String name1 @RequestParam--->zhangsan
[1]String name2 @
[2]int age @RequestParam--->18 // 字符串类型,需要进行转换
[3]String home1 @RequestParam--->${JAVA_HOME}
[4]MultipartFile file @RequestParam--->org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile@3c7f66c4
[5]int id @PathVariable
[6]String header @RequestHeader
[7]String token @CookieValue
[8]String home2 @Value
[9]HttpServletRequest request @
[10]User user1 @ModelAttribute
[11]User user2 @
[12]User user3 @RequestBody
我们发现,一共解析了三个,分别是:
@RequestParam("name1") String name1, // name1=张三
@RequestParam("age") int age, // age=18
@RequestParam(name = "home", defaultValue = "${JAVA_HOME}") String home1, // spring 获取数据
@RequestParam("file") MultipartFile file, // 上传文件
我们发现,虽然age我们是用int接收的,但是现在仍然是String类型,我们需要讲字符串类型转为int类型,因此呢,我们需要一个数据绑定转换器:
DefaultDataBinderFactory factory = new DefaultDataBinderFactory(null);
// for循环中的源代码改为:
// 解析并返回参数值
Object result = resolver.resolveArgument(parameter, container, new ServletWebRequest(multiRequest), factory);
这样就改为了int类型。
接下来我们发现${JAVA_HOME}
解析失败了,我们回忆一下,沃恩一般对于这种的值获取,一般是从Spring中获取的。此时我们并没有指定Spring容器,因此呢,我们可以在创建解析器的时候,讲Spring从其传递进去,这样就可以获取到值了:
// 获取BeanFactory
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// for循环中的代码为:
RequestParamMethodArgumentResolver resolver = new RequestParamMethodArgumentResolver(beanFactory, false);
[3]String home1 @RequestParam--->D:\development_tools\jdk17\jdk-17.0.10
我们发现解析成功了!
由于Spring中与非常多大额参数解析器,我们如果一个个添加简直太麻烦了,我们可以添加一个参数解析组合器,在这个参数解析组合器中,我们可以很方便的添加我们所需要的参数解析器,进一步屏蔽底层的具体参数解析原理:
for (MethodParameter parameter : handlerMethod.getMethodParameters()) {
// 参数解析组合器
HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
composite.addResolvers(
// 支持@RequestMapping的参数解析器
new RequestParamMethodArgumentResolver(beanFactory, false)
);
...
}
3. @PathVariable参数解析器
接下来我们看一下路径参数解析器。路径参数对应的解析器为PathVariableMethodArgumentResolver
composite.addResolvers(
// 支持@RequestMapping的参数解析器
new RequestParamMethodArgumentResolver(beanFactory, false),
// 支持对@PatherVariable的参数解析器
new PathVariableMethodArgumentResolver()
);
[5]int id @PathVariable--->123
我们发现,@PathVariable的参数解析器解析成功。其原理就是将请求的URL中的路径参数和我们写好的路径参数进行匹配。例如/test/{id}
和/test/123
之间的关系找出来,并将两者关系放到一个Map中。然后将该map集合放到Request
作用域中,等这样PathVariableMethodArgumentResolver解析器就会从Request作用域中拿到该map集合,然后再做进一步的处理。
4. RequestHeader参数解析器
@RequestHeader是根据一个请求头的key来获取该请求头的值。对于@RequestHeader对应的参数解析器为RequestHeaderMethodArgumentResolver
,我们将该请求器加入到参数组合器中:
composite.addResolvers(
// 支持@RequestMapping的参数解析器
new RequestParamMethodArgumentResolver(beanFactory, false),
// 支持对@PatherVariable的参数解析器
new PathVariableMethodArgumentResolver(),
// 支持对@RequestHeader的参数解析器
new RequestHeaderMethodArgumentResolver(beanFactory)
);
[6]String header @RequestHeader--->application/json
5. @CookieValue参数解析器
@CookieValue是根据cookie name获取cookie的值,该参数解析器为ServletCookieValueMethodArgumentResolver
,我们同样的额加一个
composite.addResolvers(
// 支持@RequestMapping的参数解析器
new RequestParamMethodArgumentResolver(beanFactory, false),
// 支持对@PatherVariable的参数解析器
new PathVariableMethodArgumentResolver(),
// 支持对@RequestHeader的参数解析器
new RequestHeaderMethodArgumentResolver(beanFactory),
// 支持对@CookieValue的参数解析器
new ServletCookieValueMethodArgumentResolver(beanFactory)
);
[7]String token @CookieValue--->123456
6. @Value参数解析器
@Value参数解析器就不是从请求中获取了,而是从Spring容器中获取。@Value可以解析#{}
, ${}
。
-
${}
一般从环境变量,配置文件中获取 -
#{}
一般是从EL表达式中获取
@Value的解析器为ExpressionValueMethodArgumentResolver
,我们在组合器中添加ExpressionValueMethodArgumentResolver
:
// 支持@Value的参数解析器
new ExpressionValueMethodArgumentResolver(beanFactory)
[8]String home2 @Value--->D:\development_tools\jdk17\jdk-17.0.10
7. HttpServletRequest参数解析
HttpServletRequest对应的解析器为ServletRequestMethodArgumentResolver
, 我们在组合器中埃及如该解析器:
// 支持对HttpRequest的参数解析器
new ServletRequestMethodArgumentResolver()
[9]HttpServletRequest request @--->org.springframework.web.multipart.support.StandardMultipartHttpServletRequest@48075da3
8. @ModelAttribute参数解析
@ModelAttributez注解的作用就是将名字等于值和我们的Java对象进行一个绑定,且将我们的参数解析器处理的结果作为一个模型数据(Model Data)存入到ModelAndViewContainer中,其对应的参数解析器为ServletModelAttributeMethodProcessor
,我们将该解析器加入到组合其中:
// 支持对@ModelAttribute的参数解析器
// 必须有@ModelAttribute注解才可以生效;false表示没有该注解该参数解析器不会解析
new ServletModelAttributeMethodProcessor(false)
[10]User user1 @ModelAttribute--->User{name='张三', age=18}
我们将ServletModelAttributeMethodProcessor
的参数改为true,表示参数上就算没有@ModelAttribute
注解也会解析:
new ServletModelAttributeMethodProcessor(true)
[10]User user1 @ModelAttribute--->User{name='张三', age=18}
[11]User user2 @--->User{name='张三', age=18}
9. @RequestBody参数解析
@RequestBody是获取请求体中的数据,其对应的解析器为RequestResponseBodyMethodProcessor
,我们将该解析器加入到组合其中:
// 支持对@Request的参数解析器,其参数需要一个消息转换器,这里我们使用Jackson提供的JSON作为消息转换器
new RequestResponseBodyMethodProcessor(List.of(new MappingJackson2HttpMessageConverter()))
[12]User user3 @RequestBody--->User{name='张三', age=18}
我们发现从json数据转为了Java对象。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构