深入分析 SpringMVC 参数解析器

2025-05-27 0 19

深入分析 SpringMVC 参数解析器

前面和大家聊了自定义 SpringMVC 参数解析器,同时我们也分析了几个比较简单的参数解析器,相信大家对于 SpringMVC 中的参数解析器应该已经有了一定的了解,如果还没看过的小伙伴可以先看看:SpringBoot 中如何自定义参数解析器?。

不过我相信很多小伙伴真正疑惑的是像下面这种接口,参数是怎么解析的:

  1. @GetMapping("/hello2")
  2. publicvoidhello2(Stringname){
  3. System.out.println("name="+name);
  4. }

抑或者像下面这种接口,参数是怎么解析的:

  1. @GetMapping("/hello/{id}")
  2. publicvoidhello3(@PathVariableLongid){
  3. System.out.println("id="+id);
  4. }

这是我们日常中最常见的参数定义方式,相信很多小伙伴对此很感兴趣。由于这块涉及到一个非常庞大的类 AbstractNamedValueMethodArgumentResolver,因此这里我单独写了一篇文章来和大家分享这个问题。

在正式分享之前,我们先来整体看看参数解析器都有哪些。

1.参数解析器

HandlerMethodArgumentResolver 就是我们口口声声说的参数解析器,它的实现类还是蛮多的,因为每一种类型的参数都对应了一个参数解析器

深入分析 SpringMVC 参数解析器

为了理解方便,我们可以将这些参数解析器分为四大类:

  • xxxMethodArgumentResolver:这就是一个普通的参数解析器
  • xxxMethodProcessor:不仅可以当作参数解析器,还可以处理对应类型的返回值。
  • xxxAdapter:这种不做参数解析,仅仅用来作为 WebArgumentResolver 类型的参数解析器的适配器。
  • HandlerMethodArgumentResolverComposite:这个看名字就知道是一个组合解析器,它是一个代理,具体代理其他干活的那些参数解析器

大致上可以分为这四类,其中最重要的当然就是前两种了。

2.参数解析器概览

接下来我们来先来大概看看这些参数解析器分别都是用来干什么的。

MapMethodProcessor

这个用来处理 Map/ModelMap 类型的参数,解析完成后返回 model。

PathVariableMethodArgumentResolver

这个用来处理使用了 @PathVariable 注解并且参数类型不为 Map 的参数参数类型为 Map 则使用 PathVariableMapMethodArgumentResolver 来处理。

PathVariableMapMethodArgumentResolver

见上。

ErrorsMethodArgumentResolver

这个用来处理 Error 参数,例如我们做参数校验时的 BindingResult。

AbstractNamedValueMethodArgumentResolver

这个用来处理 key/value 类型的参数,如请求头参数、使用了 @PathVariable 注解的参数以及 Cookie 等。

RequestHeaderMethodArgumentResolver

这个用来处理使用了 @RequestHeader 注解,并且参数类型不是 Map 的参数(参数类型是 Map 的使用 RequestHeaderMapMethodArgumentResolver)。

RequestHeaderMapMethodArgumentResolver

见上。

RequestAttributeMethodArgumentResolver

这个用来处理使用了 @RequestAttribute 注解的参数

RequestParamMethodArgumentResolver

这个功能就比较广了。使用了 @RequestParam 注解的参数、文件上传的类型 MultipartFile、或者一些没有使用任何注解的基本类型(Long、Integer)以及 String 等,都使用该参数解析器处理。需要注意的是,如果 @RequestParam 注解的参数类型是 Map,则该注解必须有 name 值,否则解析将由 RequestParamMapMethodArgumentResolver 完成。

RequestParamMapMethodArgumentResolver

见上。

AbstractCookieValueMethodArgumentResolver

这个是一个父类,处理使用了 @CookieValue 注解的参数

ServletCookieValueMethodArgumentResolver

这个处理使用了 @CookieValue 注解的参数

MatrixVariableMethodArgumentResolver

这个处理使用了 @MatrixVariable 注解并且参数类型不是 Map 的参数,如果参数类型是 Map,则使用 MatrixVariableMapMethodArgumentResolver 来处理。

MatrixVariableMapMethodArgumentResolver

见上。

SessionAttributeMethodArgumentResolver

这个用来处理使用了 @SessionAttribute 注解的参数

ExpressionValueMethodArgumentResolver

这个用来处理使用了 @Value 注解的参数

ServletResponseMethodArgumentResolver

这个用来处理 ServletResponse、OutputStream 以及 Writer 类型的参数

ModelMethodProcessor

这个用来处理 Model 类型参数,并返回 model。

ModelAttributeMethodProcessor

这个用来处理使用了 @ModelAttribute 注解的参数

SessionStatusMethodArgumentResolver

这个用来处理 SessionStatus 类型的参数

PrincipalMethodArgumentResolver

这个用来处理 Principal 类型参数,这个松哥在前面的文章中和大家介绍过了(SpringBoot 中如何自定义参数解析器?)。

AbstractMessageConverterMethodArgumentResolver

这是一个父类,当使用 HttpMessageConverter 解析 requestbody 类型参数时,相关的处理类都会继承自它。

RequestPartMethodArgumentResolver

这个用来处理使用了 @RequestPart 注解、MultipartFile 以及 Part 类型的参数

AbstractMessageConverterMethodProcessor

这是一个工具类,不承担参数解析任务。

RequestResponseBodyMethodProcessor

这个用来处理添加了 @RequestBody 注解的参数

HttpEntityMethodProcessor

这个用来处理 HttpEntity 和 RequestEntity 类型的参数

ContinuationHandlerMethodArgumentResolver

AbstractWebArgumentResolverAdapter

这种不做参数解析,仅仅用来作为 WebArgumentResolver 类型的参数解析器的适配器。

ServletWebArgumentResolverAdapter

这个给父类提供 request。

UriComponentsBuilderMethodArgumentResolver

这个用来处理 UriComponentsBuilder 类型的参数

ServletRequestMethodArgumentResolver

这个用来处理 WebRequest、ServletRequest、MultipartRequest、HttpSession、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId 类型的参数

HandlerMethodArgumentResolverComposite

这个看名字就知道是一个组合解析器,它是一个代理,具体代理其他干活的那些参数解析器

RedirectAttributesMethodArgumentResolver

这个用来处理 RedirectAttributes 类型的参数,RedirectAttributes 松哥在之前的文章中和大家介绍过:SpringMVC 中的参数还能这么传递?涨姿势了!。

好了,各个参数解析器的大致功能就给大家介绍完了,接下来我们选择其中一种,来具体说说它的源码。

3.AbstractNamedValueMethodArgumentResolver

AbstractNamedValueMethodArgumentResolver 是一个抽象类,一些键值对类型的参数解析器都是通过继承它实现的,它里边定义了很多这些键值对类型参数解析器的公共操作。

AbstractNamedValueMethodArgumentResolver 中也是应用了很多模版模式,例如它没有实现 supportsParameter 方法,该方法的具体实现在不同的子类中,resolveArgument 方法它倒是实现了,我们一起来看下:

  1. @Override
  2. @Nullable
  3. publicfinalObjectresolveArgument(MethodParameterparameter,@NullableModelAndViewContainermavContainer,
  4. NativeWebRequestwebRequest,@NullableWebDataBinderFactorybinderFactory)throwsException{
  5. NamedValueInfonamedValueInfo=getNamedValueInfo(parameter);
  6. MethodParameternestedParameter=parameter.nestedIfOptional();
  7. ObjectresolvedName=resolveEmbeddedValuesAndExpressions(namedValueInfo.name);
  8. if(resolvedName==null){
  9. thrownewIllegalArgumentException(
  10. "Specifiednamemustnotresolvetonull:["+namedValueInfo.name+"]");
  11. }
  12. Objectarg=resolveName(resolvedName.toString(),nestedParameter,webRequest);
  13. if(arg==null){
  14. if(namedValueInfo.defaultValue!=null){
  15. arg=resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
  16. }
  17. elseif(namedValueInfo.required&&!nestedParameter.isOptional()){
  18. handleMissingValue(namedValueInfo.name,nestedParameter,webRequest);
  19. }
  20. arg=handleNullValue(namedValueInfo.name,arg,nestedParameter.getNestedParameterType());
  21. }
  22. elseif("".equals(arg)&&namedValueInfo.defaultValue!=null){
  23. arg=resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
  24. }
  25. if(binderFactory!=null){
  26. WebDataBinderbinder=binderFactory.createBinder(webRequest,null,namedValueInfo.name);
  27. try{
  28. arg=binder.convertIfNecessary(arg,parameter.getParameterType(),parameter);
  29. }
  30. catch(ConversionNotSupportedExceptionex){
  31. thrownewMethodArgumentConversionNotSupportedException(arg,ex.getRequiredType(),
  32. namedValueInfo.name,parameter,ex.getCause());
  33. }
  34. catch(TypeMismatchExceptionex){
  35. thrownewMethodArgumentTypeMismatchException(arg,ex.getRequiredType(),
  36. namedValueInfo.name,parameter,ex.getCause());
  37. }
  38. //Checkfornullvalueafterconversionofincomingargumentvalue
  39. if(arg==null&&namedValueInfo.defaultValue==null&&
  40. namedValueInfo.required&&!nestedParameter.isOptional()){
  41. handleMissingValue(namedValueInfo.name,nestedParameter,webRequest);
  42. }
  43. }
  44. handleResolvedValue(arg,namedValueInfo.name,parameter,mavContainer,webRequest);
  45. returnarg;
  46. }
  1. 首先根据当前请求获取一个 NamedValueInfo 对象,这个对象中保存了参数的三个属性:参数名、参数是否必须以及参数默认值。具体的获取过程就是先去缓存中拿,缓存中如果有,就直接返回,缓存中如果没有,则调用 createNamedValueInfo 方法去创建,将创建结果缓存起来并返回。createNamedValueInfo 方法是一个模版方法,具体的实现在子类中。
  2. 接下来处理 Optional 类型参数
  3. resolveEmbeddedValuesAndExpressions 方法是为了处理注解中使用了 SpEL 表达式的情况,例如如下接口:
  1. @GetMapping("/hello2")
  2. publicvoidhello2(@RequestParam(value="${aa.bb}")Stringname){
  3. System.out.println("name="+name);
  4. }

参数名使用了表达式,那么 resolveEmbeddedValuesAndExpressions 方法的目的就是解析出表达式的值,如果没用到表达式,那么该方法会将原参数原封不动返回。4. 接下来调用 resolveName 方法解析出参数的具体值,这个方法也是一个模版方法,具体的实现在子类中。5. 如果获取到的参数值为 null,先去看注解中有没有默认值,然后再去看参数值是否是必须的,如果是,则抛异常出来,否则就设置为 null 即可。6. 如果解析出来的参数值为空字符串 "",则也去 resolveEmbeddedValuesAndExpressions 方法中走一遭。7. 最后则是 WebDataBinder 的处理,解决一些全局参数的问题,WebDataBinder 松哥在之前的文章中也有介绍过,传送门:@ControllerAdvice 的三种使用场景。

大致的流程就是这样。

在这个流程中,我们看到主要有如下两个方法是在子类中实现的:

  • createNamedValueInfo
  • resolveName

在加上 supportsParameter 方法,子类中一共有三个方法需要我们重点分析。

那么接下来我们就以 RequestParamMethodArgumentResolver 为例,来看下这三个方法。

4.RequestParamMethodArgumentResolver

4.1 supportsParameter

  1. @Override
  2. publicbooleansupportsParameter(MethodParameterparameter){
  3. if(parameter.hasParameterAnnotation(RequestParam.class)){
  4. if(Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())){
  5. RequestParamrequestParam=parameter.getParameterAnnotation(RequestParam.class);
  6. return(requestParam!=null&&StringUtils.hasText(requestParam.name()));
  7. }
  8. else{
  9. returntrue;
  10. }
  11. }
  12. else{
  13. if(parameter.hasParameterAnnotation(RequestPart.class)){
  14. returnfalse;
  15. }
  16. parameter=parameter.nestedIfOptional();
  17. if(MultipartResolutionDelegate.isMultipartArgument(parameter)){
  18. returntrue;
  19. }
  20. elseif(this.useDefaultResolution){
  21. returnBeanUtils.isSimpleProperty(parameter.getNestedParameterType());
  22. }
  23. else{
  24. returnfalse;
  25. }
  26. }
  27. }
  28. publicstaticbooleanisSimpleProperty(Class<?>type){
  29. returnisSimpleValueType(type)||(type.isArray()&&isSimpleValueType(type.getComponentType()));
  30. }
  31. publicstaticbooleanisSimpleValueType(Class<?>type){
  32. return(Void.class!=type&&void.class!=type&&
  33. (ClassUtils.isPrimitiveOrWrapper(type)||
  34. Enum.class.isAssignableFrom(type)||
  35. CharSequence.class.isAssignableFrom(type)||
  36. Number.class.isAssignableFrom(type)||
  37. Date.class.isAssignableFrom(type)||
  38. Temporal.class.isAssignableFrom(type)||
  39. URI.class==type||
  40. URL.class==type||
  41. Locale.class==type||
  42. Class.class==type));
  43. }

从 supportsParameter 方法中可以非常方便的看出支持的参数类型:

  1. 首先参数如果有 @RequestParam 注解的话,则分两种情况:参数类型如果是 Map,则 @RequestParam 注解必须配置 name 属性,否则不支持;如果参数类型不是 Map,则直接返回 true,表示总是支持(想想自己平时使用的时候是不是这样)。
  2. 参数如果含有 @RequestPart 注解,则不支持。
  3. 检查下是不是文件上传请求,如果是,返回 true 表示支持。
  4. 如果前面都没能返回,则使用默认的解决方案,判断是不是简单类型,主要就是 Void、枚举、字符串、数字、日期等等。
  5. 这块代码其实很简单,支持谁不支持谁,一目了然。

4.2 createNamedValueInfo

  1. @Override
  2. protectedNamedValueInfocreateNamedValueInfo(MethodParameterparameter){
  3. RequestParamann=parameter.getParameterAnnotation(RequestParam.class);
  4. return(ann!=null?newRequestParamNamedValueInfo(ann):newRequestParamNamedValueInfo());
  5. }
  6. privatestaticclassRequestParamNamedValueInfoextendsNamedValueInfo{
  7. publicRequestParamNamedValueInfo(){
  8. super("",false,ValueConstants.DEFAULT_NONE);
  9. }
  10. publicRequestParamNamedValueInfo(RequestParamannotation){
  11. super(annotation.name(),annotation.required(),annotation.defaultValue());
  12. }
  13. }

获取注解,读取注解中的属性,构造 RequestParamNamedValueInfo 对象返回。

4.3 resolveName

  1. @Override
  2. @Nullable
  3. protectedObjectresolveName(Stringname,MethodParameterparameter,NativeWebRequestrequest)throwsException{
  4. HttpServletRequestservletRequest=request.getNativeRequest(HttpServletRequest.class);
  5. if(servletRequest!=null){
  6. ObjectmpArg=MultipartResolutionDelegate.resolveMultipartArgument(name,parameter,servletRequest);
  7. if(mpArg!=MultipartResolutionDelegate.UNRESOLVABLE){
  8. returnmpArg;
  9. }
  10. }
  11. Objectarg=null;
  12. MultipartRequestmultipartRequest=request.getNativeRequest(MultipartRequest.class);
  13. if(multipartRequest!=null){
  14. List<MultipartFile>files=multipartRequest.getFiles(name);
  15. if(!files.isEmpty()){
  16. arg=(files.size()==1?files.get(0):files);
  17. }
  18. }
  19. if(arg==null){
  20. String[]paramValues=request.getParameterValues(name);
  21. if(paramValues!=null){
  22. arg=(paramValues.length==1?paramValues[0]:paramValues);
  23. }
  24. }
  25. returnarg;
  26. }

这个方法思路也比较清晰:

  1. 前面两个 if 主要是为了处理文件上传请求。
  2. 如果不是文件上传请求,则调用 request.getParameterValues 方法取出参数返回即可。

整个过程还是比较 easy 的。小伙伴们可以在此基础之上自行分析 PathVariableMethodArgumentResolver 的原理,也很容易。

5.小结

今天主要和小伙伴们梳理了 SpringMVC 参数解析器的整个体系,关于这些解析器在何时被配置,在何时被调用,松哥在后面的文章中会和大家继续分析。好啦,今天就说这么多。

原文地址:https://mp.weixin.qq.com/s/qsS6Dwr6eXx07i5dhn8FEA

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

快网idc优惠网 建站教程 深入分析 SpringMVC 参数解析器 https://www.kuaiidc.com/76575.html

相关文章

发表评论
暂无评论