Web请求中,我们将参数放在地址栏或请求体中,个别请求可能放在请求头中。放在地址栏中,我们可以获取参数如下:Stringjavaboy=request.getParameter("name");放到requestbody中,如果是key/value形式,我们可以这样获取参数:Stringjavaboy=request.getParameter("名称");如果是JSON形式,我们可以通过下面的方法获取输入流,然后解析成JSON字符串,再通过JSON工具转换成对象:BufferedReaderreader=newBufferedReader(newInputStreamReader(request.getInputStream()));Stringjson=reader.readLine();reader.close();Useruser=newObjectMapper().readValue(json,User.class);如果该参数放在请求头中,我们可以通过如下方式获取:Stringjavaboy=request.getHeader("姓名");如果你使用的是Jsp/Servlet技术栈,那么参数的获取无非就是这些方法。如果使用SpringMVC框架,可能有些朋友会觉得参数获取方式太丰富了。@RequestParam、@RequestBody、@RequestHeader、@PathVariable等各种注解,参数可以是key/value或者JSON的形式,非常丰富!不过,再丰富,最底层的获取参数的方式也无外乎以上几种。那么有小伙伴要问了,SpringMVC是如何把请求中的参数提取出来直接给我们使用的呢?比如下面这个接口:@RestControllerpublicclassHelloController{@GetMapping("/hello")publicStringhello(Stringname){return"hello"+name;}}我们都知道name参数是从HttpServletRequest中提取出来的,它是怎么提取出来的呢?这就是宋歌今天要跟大家分享的话题。1.自定义参数解析器为了澄清此问题,让我们首先定义一个参数解析器。自自定义参数参数解析器需要需要需要实现实现实现实现实现实现实现实现实现实现实现实现实现参数我们我们我们我们我们先先:publicInterInterInterInterInterInterInterInterThandLermhandLermEthodArmethodargumentRermethodArmethodArmetHodareAdareAdareAdareAdareAdareAnsueansupparameter(modectparameterparameterparameterparameterparameter;NullableModelAndViewContainermavContainer,NativeWebRequestwebRequest,@NullableWebDataBinderFactorybinderFactory)throwsException;}这个接口中就两个方法:supportsParameter:该方法表示是否开启该参数解析器,返回true表示开启,返回false表示不开启。resolveArgument:这是具体的解析过程,即从请求中提取参数的过程,方法的返回值对应接口中参数的值。自定义参数解析器只需要实现这个接口。假设我现在有这样一个需求(其实在SpringSecurity中获取当前登录用户名是很方便的,这里只是针对这种情况,不要抬杠):假设我使用SpringSecurity做系统安全现在框架(不熟悉SpringSecurity的朋友,可以在公众号江南一电鱼后台回复ss,里面有教程),如果我在接口的参数上加上@CurrentUserName注解,那么参数的值就是当前登录的用户名,像这样:@RestControllerpublicclassHelloController{@GetMapping("/hello")publicStringhello(@CurrentUserNameStringname){return"hello"+name;}}要实现这个功能,很简单,首先我们自定义一个@CurrentUserName注解,如下:@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.PARAMETER)public@interfaceCurrentUserName{}这个注解没什么好解释的。接下来我们自定义参数解析器CurrentUserNameHandlerMethodArgumentResolver,如下:publicclassCurrentUserNameHandlerMethodArgumentResolverimplementsHandlerMethodArgumentResolver{@OverridepublicbooleansupportsParameter(MethodParameterparameter){returnparameter.getParameterType().isAssignableFrom(String.class)&¶meter.hasParameterAnnotation(CurrentUserName.class);}@OverridepublicObjectresolveArgument(MethodParameterparameter,ModelAndViewContainermavContainer,NativeWebRequestwebRequest,WebDataBinderFactorybinderFactory)throwsException{Useruser=(User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();returnuser.getUsername();}}supportsParameter:如果参数类型为String且参数上有@CurrentUserName注解,使用参数解析器。resolveArgument:该方法的返回值是参数的具体值。当前登录用户名可以从SecurityContextHolder中获取(具体参数见松哥的SpringSecurity教程,公众号后台回复ss)。最后,我们将自定义参数解析器配置到HandlerAdapter中,如下:@ConfigurationpublicclassWebConfigimplementsWebMvcConfigurer{@OverridepublicvoidaddArgumentResolvers(Listresolvers){resolvers.add(newCurrentUserNameHandlerMethodArgumentResolver());}}至此,就算配置完成了。接下来,启动项目。用户登录成功后,访问/hello接口,可以看到返回了当前登录的用户数据。这是我们的自定义参数类型解析器。如您所见,这非常容易。在SpringMVC中,默认有很多HandlerMethodArgumentResolver的实现类,它们处理的问题也大同小异。宋弟兄再举个例子。2.PrincipalMethodArgumentResolver如果我们在项目中使用了SpringSecurity,我们可以通过以下方式获取当前登录用户信息:@GetMapping("/hello2")publicStringhello2(Principalprincipal){return"hello"+principal.getName();}即直接在当前接口的参数中增加一个Principal类型的参数即可。该参数描述了当前登录用户的信息。用过SpringSecurity的朋友应该知道这一点(对SpringSecurity不熟悉的朋友可以在公众号【江南有点雨】后台回复ss找到)。那么这个功能是如何实现的呢?当然,PrincipalMethodArgumentResolver正在工作!Let'stakealookatthisparameterparser:publicclassPrincipalMethodArgumentResolverimplementsHandlerMethodArgumentResolver{@OverridepublicbooleansupportsParameter(MethodParameterparameter){returnPrincipal.class.isAssignableFrom(parameter.getParameter)}OverridepublicObjectresolveArgument(MethodParameterparameter,@NullableModelAndViewContainermavContainer,NativeWebRequestwebRequest,@NullableWebDataBinderFactorybinderFactory)throwsException{HttpServletRequestrequest=webRequest.getNativeRequest(HttpServletRequest.class);if(request==null){thrownewIllegalStateException("CurrentrequestisnotoftypeHttpServletRequest:"+webRequest);}Principalprincipal=request.getUserPrincipal();if(principal!=null&&!parameter.getParameterType().isInstance(principal)){thrownewIllegalStateException("Currentuserprincipalisnotoftype["+parameter.getParameterType().getName()+"]:"+principal);}returnprincipal;}}supportsParameter:该方法主要是判断参数类型是否为Principal。如果参数类型是Principal,则支持resolveArgument:这个方法的逻辑很简单。先获取原始请求,然后从请求中获取Principal对象并返回。.是不是很简单,有了这个,我们就可以随时加载当前登录的用户信息了。3、RequestParamMapMethodArgumentResolver松哥再给大家举个例子:@RestControllerpublicclassHelloController{@PostMapping("/hello")publicvoidhello(@RequestParamMultiValueMapmap)throwsIOException{//省略...}}这个接口可能很多小伙伴都写过,使用MapTo从前端接收参数,这里使用的参数解析器是RequestParamMapMethodArgumentResolver。publicclassRequestParamMapMethodArgumentResolverimplementsHandlerMethodArgumentResolver{@OverridepublicbooleansupportsParameter(MethodParameterparameter){RequestParamrequestParam=parameter.getParameterAnnotation(RequestParam.class);返回(requestParam!=null&&Map.class.isAssignableFrom(parameter.getParameterType())&&!String.Name(request.Param);}@OverridepublicObjectresolveArgument(MethodParameterparameter,@NullableModelAndViewContainermavContainer,NativeWebRequestwebRequest,@NullableWebDataBinderFactorybinderFactory)throwsException{ResolvableTyperesolvableType=ResolvableType.forMethodParameter(参数);如果(MultiValueMap.class.isAssignableFrom(parameter.getParameterType?Multiable()Value)){/.as(MultiValueMap.class).getGeneric(1).resolve();if(valueType==MultipartFile.class){MultipartRequestmultipartRequest=MultipartResolutionDelegate.resolveMultipartRequest(webRequest);返回(multipartRequest!=null?multipartRequest.getMultiFileMap():newLinkedMultiValueMap<>(0));}elseif(valueType==Part.class){HttpServletRequestservletRequest=webRequest.getNativeRequest(HttpServletRequest.class);if(servletRequest!=null&&MultipartResolutionDelegate.isMultipartRequest(servletRequest)){Collectionparts=servletRequest.getParts();LinkedMultiValueMapresult=newLinkedMultiValueMap<>(parts.size());for(Partpart:parts){result.add(part.getName(),part);}returnresult;}returnnewLinkedMultiValueMap<>(0);}else{MapparameterMap=webRequest.getParameterMap();MultiValueMapresult=newLinkedMultiValueMap<>(parameterMap.size());parameterMap.forEach((key,values)->{for(Stringvalue:values){result.add(key,value);}});returnresult;}}else{//RegularMapClass>valueType=resolvableType.asMap().getGeneric(1).resolve();if(valueType==MultipartFile.class){MultipartRequestmultipartRequest=MultipartResolutionDelegate.resolveMultipartRequest(webRequest);返回(multipartRequest!=null?multipartRequest.getFileMap():newLinkedHashMap<>(0));}elseif(valueType==Part.class){HttpServletRequestservletRequest=webRequest.getNativeRequest(HttpServletRequest.class);if(servletRequest!=null&&MultipartResolutionDelegate.isMultipartRequest(servletRequest)){Collectionparts=servletRequest.getParts();LinkedHashMapresult=CollectionUtils.newLinkedHashMap(parts.size());for(Partpart:部分){if(!result.containsKey(part.getName())){result.put(part.getName(),part);}}returnresult;}returnnewLinkedHashMap<>(0);}else{MapparameterMap=webRequest.getParameterMap();Mapresult=CollectionUtils.newLinkedHashMap(parameterMap.size());parameterMap.forEach((key,values)->{if(values.length>0){result.put(key,values[0]);}});returnresult;}}}}支持参数:参数类型是一个Map,并且使用了@RequestParam注解,@RequestParam注解中没有配置name属性,可以使用参数解析器resolveArgument:具体解析分为两种情况:MultiValueMap和其他Map,以及前者分为三种情况:MultipartFile、Part或其他普通请求,前两种可以处理文件上传,第三种是普通参数。如果是普通的Map,可以直接获取原始请求参数,放到一个Map集合中返回。4.总结前面跟大家讲了几种简单的情况,复杂的还有PathVariableMethodArgumentResolver、RequestParamMethodArgumentResolver等。后面宋哥会和大家详细说。同时,还有一个问题就是这些参数解析器是在哪里调用的。这个也会在松哥近期的SpringMVC源码分析系列中分享给大家。好了,这个周末,就这些简单的知识,祝大家周末愉快~本文转载自微信公众号“江南的一场小雨”,可以通过以下二维码关注。转载本文请联系江南一点鱼公众号。