第二十一讲-不同的参数解析器

第二十一讲-不同的参数解析器

前面我们在介绍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 {
}
  1. 测试的请求对象:
// 测试的请求对象:
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);
}
  1. 我们将自定义的控制器中的方法封装为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))
  1. 准备一个对象绑定与类型转换的工具,主要用于字符串到其它类型的转换,这里暂时不加入。

  2. 准备一个ModelAndViewContainer来存储中间Model的结果

ModelAndViewContainer container = new ModelAndViewContainer();
  1. 最后我们测试这些参数解析器,当然,在测试这些参数解析器之前,我们把这些参数解析器遍历出来:
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对象。

posted @   LilyFlower  阅读(10)  评论(0编辑  收藏  举报
编辑推荐:
· .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语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示