Java充电社
专辑
博文
联系我
本人继续续收门徒,亲手指导
SpringMVC教程
-> 参数解析器HandlerMethodArgumentResolver解密
1、Helloword
2、@Controller、@RequestMapping
3、接口测试利器
4、如何接受请求中的参数?
5、@RequestBody接收Json格式数据
6、多文件上传
7、返回页面常见的5种方式
8、返回json格式数据 & 通用返回值设计
9、SpringMVC返回null是什么意思?
10、异步处理请求
11、如何集成静态资源?
12、拦截器怎么用?
13、统一异常处理
14、实战篇:通用返回值 & 异常处理设计
15、全注解的方式 & 原理解析
16、源码解析SpringMVC处理请求的流程
17、源码解析SpringMVC容器的启动过程
18、RequestBodyAdvice:对@ReuqestBody进行增强
19、ResponseBodyAdvice:对@ResponseBody进行增强
20、RESTful接口详解
21、接口调用利器RestTemplate
22、参数解析器HandlerMethodArgumentResolver解密
23、@RequestParam用法及原理详解
24、@RequestBody原理解密
25、@RequestHeader详解
26、@CookieValue详解
27、@RequestAttribute详解
28、@SessionAttribute详解
29、重定向和转向详解
30、Converter转换器详解
31、跨域问题详解
32、类容协商,颠覆你的认知
33、终章
34、CORS通信
35、浏览器安全策略 & CORS
36、Http中的Content-Type详解
上一篇:接口调用利器RestTemplate
下一篇:@RequestParam用法及原理详解
<div style="display:none"></div> 大家好,我是路人,这是SpringMVC系列第22篇。 ## 1、来看2个好问题 大家在使用SpringMVC或者SpringBoot开发接口的时候,有没有思考过下面这2个问题 - 接口的参数到底支持哪些类型?有什么规律可循么? - 接口参数的值是从哪里来的呢? 说实话,这2个问题非常关键,搞懂原理之后,开发接口将得心应手,今天就带大家从原理上来搞懂这俩问题。 ## 2、SpringMVC处理请求大概的过程 step1、接受请求 step2、根据请求信息找到能够处理请求的控制器方法 step3、解析请求,组装控制器方法需要的参数的值 step4、通过反射调用送控制器方法 step5、响应结果等 咱们重点来看step3参数值组装这个过程。 ## 3、解析处理器方法参数的值 解析参数需要的值,SpringMVC中专门有个接口来干这个事情,这个接口就是:**HandlerMethodArgumentResolver**,中文称呼:处理器放放参数解析器,说白了就是解析请求得到Controller方法的参数的值。 ### 3.1、处理器方法参数解析器:HandlerMethodArgumentResolver接口 ```java public interface HandlerMethodArgumentResolver { /** * 判断当前解析器是否支持解析parameter这种参数 * parameter:方法参数信息 */ 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循环,简化版的过程如下: ```java //1.得到控制器参数列表 List<MethodParameter> parameterList; //2.参数解析器列表 List<HandlerMethodArgumentResolver> 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; } } } ``` 解析参数源码的位置: ```java org.springframework.web.method.support.InvocableHandlerMethod#getMethodArgumentValues ``` ## 4、常见的HandlerMethodArgumentResolver 大家可以在`InvocableHandlerMethod#getMethodArgumentValues`这个位置设置断点,可以详细了解参数解析的过程,debug中我们可以在这看到SpringMVC中默认情况下注册了这么多解析器,如下图: ![](https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/233/8a8c50cc-f80e-45f7-ac55-91fa2171dacf.png) 如下表,列出了一些常见的,以及这些参数解析器能够解析的参数的特点及类型 | 实现类 | 支持的参数类型 | 参数值 | | ---------------------------------------- | ------------------------------------------------------------ | ----------------------- | | 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类型 | cookie | | ModelMethodProcessor | 参数为Model类型,控制器中可以调用model.addAttribute想模型中放数据,最终这些数据都会通过request.setAttribute复制到request中 | 来源于SpringMVC容器 | | MapMethodProcessor | 参数为Map类型,值同ModelMethodProcessor | 来源于SpringMVC容器 | | ModelAttributeMethodProcessor | 参数需要使用@ModelAttribute标注 | Model.getAttribute | | ServletRequestMethodArgumentResolver | 参数类型为WebRequest、ServletRequest、MultipartRequest、HttpSession、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId | Servlet容器中的request | | ServletResponseMethodArgumentResolver | 参数类型是ServletResponse、OutputStream、Writer | Servlet容器中的response | | ModelMethodProcessor | 参数为org.springframework.ui.Model类型 | 来源于SpringMVC容器 | | RequestAttributeMethodArgumentResolver | 参数需使用@RequestAttribute | request.getAttribute | | SessionAttributeMethodArgumentResolver | 参数需使用@SessionAttribute | session.getAttribute | | ExpressionValueMethodArgumentResolver | 参数需使用@Value标注 | 从Spring配置中取值 | | ServletModelAttributeMethodProcessor | 支持为我们自定义的javabean赋值 | - | | RequestResponseBodyMethodProcessor | 参数需使用@RequestBody标注 | http请求中的body | | HttpEntityMethodProcessor | 参数类型为HttpEntity或RequestEntity类型,这两种类型的参数基本上包含了请求的所有参数信息 | http请求中的完整信息 | 实现类比较多,就不一一说了,这里教大家一招,让大家学会如何看每种参数解析器的源码,掌握看源码之后,大家把每个实现类的源码过一下,基本上就知道如何使用了,这里以`RequestParamMethodArgumentResolver`源码为例来做解读。 ## 5、RequestParamMethodArgumentResolver源码解读 ### 5.1、supportsParameter方法:判断支持参数类型 源码如下,挺简单的,大家注意看注释,秒懂 ```java 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(); /** * 参数微信是否为下面这些类型,通常文件上传的时候用这种类型接受参数 * MultipartFile、Collection<MultipartFile>、List<MultipartFile>、MultipartFile[] * Part、Collection<Part>、List<Part>、Part[] */ 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`从参数中取值 ```java 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<MultipartFile> 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点个赞,注释中详细说明了其用法,大家注意下面匡红的部分,稍后用一个案例代码让大家了解其他常见几种用法,这个注解的用法掌握了,其他的注解都是雷同的,大家去看起源码以及对应的参数解析器,就会秒懂了。 ![](https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/233/d35e6f86-83f1-4b43-8b57-79bf0e920a96.png) ```java @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RequestParam { /** * 对应request中参数名称 */ @AliasFor("name") String value() default ""; /** * 同value */ @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<String,List<String>> ```java @RequestMapping("/test1") @ResponseBody public Map<String, Object> test1(@RequestParam("name") String name, @RequestParam("age") int age, @RequestParam("p1") String[] p1Map, @RequestParam Map<String, String> requestParams1, @RequestParam MultiValueMap requestParams2) { //MultiValueMap相当于Map<String,List<String>> Map<String, Object> 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; } ``` 发送请求 ```html http://localhost:8080/chat17/test1?name=ready&age=35&p1=1&p1=2&p1=3 ``` 接口输出 ```json { "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地址 ```java https://gitee.com/javacode2018/springmvc-series ``` ### 8.2、本文案例代码结构说明 ![](https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/233/2d8d71f3-b13c-475e-8425-3b17f57bf1ff.png) <a style="display:none" target="_blank" href="https://mp.weixin.qq.com/s/_S1DD2JADnXvpexxaBwLLg" style="color:red; font-size:20px; font-weight:bold">继续收门徒,亲手带,月薪 4W 以下的可以来找我</a> ## 最新资料 1. <a href="https://mp.weixin.qq.com/s?__biz=MzkzOTI3Nzc0Mg==&mid=2247484964&idx=2&sn=c81bce2f26015ee0f9632ddc6c67df03&scene=21#wechat_redirect" target="_blank">尚硅谷 Java 学科全套教程(总 207.77GB)</a> 2. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247484192&idx=1&sn=505f2faaa4cc911f553850667749bcbb&scene=21#wechat_redirect" target="_blank">2021 最新版 Java 微服务学习线路图 + 视频</a> 3. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247484573&idx=1&sn=7f3d83892186c16c57bc0b99f03f1ffd&scene=21#wechat_redirect" target="_blank">阿里技术大佬整理的《Spring 学习笔记.pdf》</a> 4. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247484544&idx=2&sn=c1dfe907cfaa5b9ae8e66fc247ccbe84&scene=21#wechat_redirect" target="_blank">阿里大佬的《MySQL 学习笔记高清.pdf》</a> 5. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247485167&idx=1&sn=48d75c8e93e748235a3547f34921dfb7&scene=21#wechat_redirect" target="_blank">2021 版 java 高并发常见面试题汇总.pdf</a> 6. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247485664&idx=1&sn=435f9f515a8f881642820d7790ad20ce&scene=21#wechat_redirect" target="_blank">Idea 快捷键大全.pdf</a> ![](https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/1/2883e86e-3eff-404a-8943-0066e5e2b454.png)
#custom-toc-container