带你解密:SpringBoot接口到底支持多少种类型的参数?

带你解密:SpringBoot接口到底支持多少种类型的参数?,第1张

带你解密:SpringBoot接口到底支持多少种类型的参数

大家好,我是路人,这是 SpringMVC 系列第 22 篇。

目前所有系列文章已同步至个人博客(itsoku.com),个人博客已改版,更方便翻阅,点击文末左边的阅读原文直达博客。

1、来看 2 个好问题

大家在使用 SpringMVC 或者 SpringBoot 开发接口的时候,有没有思考过下面这 2 个问题

  • 接口的参数到底支持哪些类型?有什么规律可循么?

  • 接口参数的值是从哪里来的呢?

说实话,这 2 个问题非常关键,搞懂原理之后,开发接口将得心应手,今天就带大家从原理上来搞懂这俩问题。

2、SpringMVC 处理请求大概的过程

step1、接受请求

step2、根据请求信息找到能够处理请求的控制器方法

step3、解析请求,组装控制器方法需要的参数的值

step4、通过反射调用送控制器方法

step5、响应结果等

咱们重点来看 step3 参数值组装这个过程。

3、解析处理器方法参数的值

解析参数需要的值,SpringMVC 中专门有个接口来干这个事情,这个接口就是:HandlerMethodArgumentResolver,中文称呼:处理器放放参数解析器,说白了就是解析请求得到 Controller 方法的参数的值。

3.1、处理器方法参数解析器:HandlerMethodArgumentResolver 接口
public interface HandlerMethodArgumentResolver {

 
 boolean supportsParameter(MethodParameter parameter);

 
 @Nullable
 Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
   NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;

}
3.1、解析参数值的过程

SpringMVC 中会配置多个 HandlerMethodArgumentResolver,组成一个 HandlerMethodArgumentResolver 列表,用这个列表来解析参数得到参数需要的值,相当于 2 嵌套 for 循环,简化版的过程如下:

//1.得到控制器参数列表
List parameterList;
//2.参数解析器列表
List handlerMethodArgumentResolverList;
//控制器方法参数
Object[] handlerMethodArgs = new Object[parameterList.size()];
int paramIndex = 0;
//遍历参数列表
for (MethodParameter parameter : parameterList) {
    //遍历处理器方法参数解析器列表
    for (HandlerMethodArgumentResolver resolver : handlerMethodArgumentResolverList) {
        if (resolver.supportsParameter(parameter)) {
            handlerMethodArgs[paramIndex++] = resolver.resolveArgument(parameter, webRequest, binderFactory);
            break;
        }
    }
}

解析参数源码的位置:

org.springframework.web.method.support.InvocableHandlerMethod#getMethodArgumentValues
4、常见的 HandlerMethodArgumentResolver

大家可以在InvocableHandlerMethod#getMethodArgumentValues这个位置设置断点,可以详细了解参数解析的过程,debug 中我们可以在这看到 SpringMVC 中默认情况下注册了这么多解析器,如下图:

如下表,列出了一些常见的,以及这些参数解析器能够解析的参数的特点及类型

实现类支持的参数类型参数值RequestParamMethodArgumentResolver参数需使用@RequestParam 标注,且 name 属性有值,参数通常为普通类型、Map 类型;或 MultipartFile、Part 类型,或 MultipartFile、Part 这两种类型的集合、数组请求参数RequestParamMapMethodArgumentResolver参数需使用@RequestParam 标注,且 name 属性没有子,参数为 Map 类型;参数的值从 request 的参数中取值,Map 中的 key 对应参数名称,value 对应参数的值请求参数PathVariableMapMethodArgumentResolver参数需使用@PathVariable 标注,参数通常为普通类型从 url 中取值RequestHeaderMethodArgumentResolver参数需使用@RequestHeader 标注,参数通常为 Map、MultiValueMap、HttpHeaders 类型请求头ServletcookievalueMethodArgumentResolver参数需使用@cookievalue 标注,参数为普通类型或者 cookie 类型cookieModelMethodProcessor参数为 Model 类型,控制器中可以调用 model.addAttribute 想模型中放数据,最终这些数据都会通过 request.setAttribute 复制到 request 中来源于 SpringMVC 容器MapMethodProcessor参数为 Map 类型,值同 ModelMethodProcessor来源于 SpringMVC 容器ModelAttributeMethodProcessor参数需要使用@ModelAttribute 标注Model.getAttributeServletRequestMethodArgumentResolver参数类型为 WebRequest、ServletRequest、MultipartRequest、HttpSession、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneIdServlet 容器中的 requestServletResponseMethodArgumentResolver参数类型是 ServletResponse、OutputStream、WriterServlet 容器中的 responseModelMethodProcessor参数为 org.springframework.ui.Model 类型来源于 SpringMVC 容器RequestAttributeMethodArgumentResolver参数需使用@RequestAttributerequest.getAttributeSessionAttributeMethodArgumentResolver参数需使用@SessionAttributesession.getAttributeexpressionValueMethodArgumentResolver参数需使用@Value 标注从 Spring 配置中取值ServletModelAttributeMethodProcessor支持为我们自定义的 javabean 赋值-RequestResponseBodyMethodProcessor参数需使用@RequestBody 标注http 请求中的 bodyHttpEntityMethodProcessor参数类型为 HttpEntity 或 RequestEntity 类型,这两种类型的参数基本上包含了请求的所有参数信息http 请求中的完整信息

实现类比较多,就不一一说了,这里教大家一招,让大家学会如何看每种参数解析器的源码,掌握看源码之后,大家把每个实现类的源码过一下,基本上就知道如何使用了,这里以RequestParamMethodArgumentResolver源码为例来做解读。

5、RequestParamMethodArgumentResolver 源码解读 5.1、supportsParameter 方法:判断支持参数类型

源码如下,挺简单的,大家注意看注释,秒懂

public boolean supportsParameter(MethodParameter parameter) {
    //判断参数上是否有@RequestParam注解
    if (parameter.hasParameterAnnotation(RequestParam.class)) {
        //参数是Map类型
        if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
            //@RequestParam注解name必须有值
            RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
            return (requestParam != null && StringUtils.hasText(requestParam.name()));
        } else {
            return true;
        }
    } else {
        //判断参数上是否有@RequestPart注解,有则返回false
        if (parameter.hasParameterAnnotation(RequestPart.class)) {
            return false;
        }
        parameter = parameter.nestedIfOptional();
        
        if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
            return true;
        } else if (this.useDefaultResolution) {
            // 是否开启了默认解析,useDefaultResolution默认是false
            return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
        } else {
            return false;
        }
    }
}
5.2、resolveArgument 方法

resolveArgument 方法最终会调用RequestParamMethodArgumentResolver#resolveName方法,代码如下,如果是文件上传的,就获取的是 MultipartFile 对象,否则就是调用request.getParameterValues从参数中取值

protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
    HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);

    Object arg = null;
    MultipartRequest multipartRequest = request.getNativeRequest(MultipartRequest.class);
    if (multipartRequest != null) {
        List files = multipartRequest.getFiles(name);
        if (!files.isEmpty()) {
            arg = (files.size() == 1 ? files.get(0) : files);
        }
    }
    if (arg == null) {
        String[] paramValues = request.getParameterValues(name);
        if (paramValues != null) {
            arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
        }
    }
    return arg;
}
5、@RequestParam:取请求中的参数 5.1、简介

@RequestParam 注解我们用到的比较多,被这个注解标注的参数,会从 request 的请求参数中取值,参数值为 request.getParameter("@RequestParam 注解 name 的值")

重点来看下这个类的源码,如下,大家要学会看源码中的注释,Spring 注释写的特别的好,这里给 spring 点个赞,注释中详细说明了其用法,大家注意下面匡红的部分,稍后用一个案例代码让大家了解其他常见几种用法,这个注解的用法掌握了,其他的注解都是雷同的,大家去看起源码以及对应的参数解析器,就会秒懂了。

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@documented
public @interface RequestParam {

 
 @AliasFor("name")
 String value() default "";

 
 @AliasFor("value")
 String name() default "";

 
 boolean required() default true;

 
 String defaultValue() default ValueConstants.DEFAULT_NONE;

}
5.2、案例

案例代码如下,注意 5 个参数,这 5 个参数反应了@RequestParam所有的的用法,这个接口的参数解析会用到 2 个解析器:RequestParamMethodArgumentResolver和RequestParamMapMethodArgumentResolver,大家可以设置断点 debug 一下。

注意最后一个参数的类型是 MultiValueMap,这种类型相当于 Map>

@RequestMapping("/test1")
@ResponseBody
public Map test1(@RequestParam("name") String name,
                                 @RequestParam("age") int age,
                                 @RequestParam("p1") String[] p1Map,
                                 @RequestParam Map requestParams1,
                                 @RequestParam MultiValueMap requestParams2) { //MultiValueMap相当于Map>
    Map result = new linkedHashMap<>();
    result.put("name", name);
    result.put("age", age);
    result.put("p1Map", p1Map);
    result.put("requestParams1", requestParams1);
    result.put("requestParams2", requestParams2);
    return result;
}

发送请求

http://localhost:8080/chat17/test1?name=ready&age=35&p1=1&p1=2&p1=3

接口输出

{
 "name": "ready",
 "age": 35,
 "p1Map": [
  "1",
  "2",
  "3"
 ],
 "requestParams1": {
  "name": "ready",
  "age": "35",
  "p1": "1"
 },
 "requestParams2": {
  "name": [
   "ready"
  ],
  "age": [
   "35"
  ],
  "p1": [
   "1",
   "2",
   "3"
  ]
 }
}
7、总结

本文带大家了解了参数解析器HandlerMethodArgumentResolver的作用,掌握这个之后,大家就知道控制器的方法中参数的写法,建议大家下去之后,多翻翻这个接口的实现类,掌握常见的参数的各种用法,这样出问题了,才能够快速定位问题,提升快速解决问题的能力。

8、代码位置及说明 8.1、git 地址
https://gitee.com/javacode2018/springmvc-series
8.2、本文案例代码结构说明

9、SpringMVC 系列目录
  1. SpringMVC 系列第 1 篇:helloword

  2. SpringMVC 系列第 2 篇:@Controller、@RequestMapping

  3. SpringMVC 系列第 3 篇:异常高效的一款接口测试利器

  4. SpringMVC 系列第 4 篇:controller 常见的接收参数的方式

  5. SpringMVC 系列第 5 篇:@RequestBody 大解密,说点你不知道的

  6. SpringMVC 系列第 6 篇:上传文件的 4 种方式,你都会么?

  7. SpringMVC 系列第 7 篇:SpringMVC 返回视图常见的 5 种方式,你会几种?

  8. SpringMVC 系列第 8 篇:返回 json & 通用返回值设计

  9. SpringMVC 系列第 9 篇:SpringMVC 返回 null 是什么意思?

  10. SpringMVC 系列第 10 篇:异步处理

  11. SpringMVC 系列第 11 篇:集成静态资源

  12. SpringMVC 系列第 12 篇:拦截器

  13. SpringMVC 系列第 13 篇:统一异常处理

  14. SpringMVC 系列第 14 篇:实战篇:通用返回值 & 异常处理设计

  15. SpringMVC 系列第 15 篇:全注解的方式  &  原理解析

  16. SpringMVC 系列第 16 篇:通过源码解析 SpringMVC 处理请求的流程

  17. SpringMVC 系列第 17 篇:源码解析 SpringMVC 容器的启动过程

  18. SpringMVC 系列第 18 篇:强大的 RequestBodyAdvice 解密

  19. SpringMVC 系列第 19 篇:强大的 ResponseBodyAdvice 解密

  20. SpringMVC 系列第 20 篇:RestFull 详解

  21. SpringMVC 系列第 21 篇:接口调用利器 RestTemplate

10、更多系列文章
  1. Spring 高手系列(共 56 篇)

  2. Java 高并发系列(共 34 篇)

  3. MySql 高手系列(共 27 篇)

  4. Maven 高手系列(共 10 篇)

  5. Mybatis 系列(共 12 篇)

  6. 聊聊 db 和缓存一致性常见的实现方式

  7. 接口幂等性这么重要,它是什么?怎么实现?

  8. 泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!

11、最新资料
  1. 尚硅谷 Java 学科全套教程(总 207.77GB)

  2. 2021 最新版 Java 微服务学习线路图 + 视频

  3. 阿里技术大佬整理的《Spring 学习笔记.pdf》

  4. 阿里大佬的《MySQL 学习笔记高清.pdf》

  5. 2021 版 java 高并发常见面试题汇总.pdf

  6. Idea 快捷键大全.pdf

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5481295.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-12
下一篇 2022-12-12

发表评论

登录后才能评论

评论列表(0条)

保存