简单分析 SpringMVC 参数解析器
本文最后更新于 2025-04-28,文章内容可能已经过时。
概述
之前在实现支持多 @RequestBody 的自定义注解 | 今日说码的时候了解了 SpringMVC 的参数解析器以及部分实现类,这里记录下学习过程。
参数解析器概览
HandlerMethodArgumentResolver 是参数解析器的接口,所有参数解析器都要实现该接口,它有非常多的实现类:
为了理解方便,我们可以将这些参数解析器分为四大类:
xxxMethodArgumentResolver:这就是一个普通的参数解析器。
xxxMethodProcessor:不仅可以当作参数解析器,还可以处理对应类型的返回值。
xxxAdapter:这种不做参数解析,仅仅用来作为 WebArgumentResolver 类型的参数解析器的适配器。
HandlerMethodArgumentResolverComposite:这是一个组合解析器,它是一个代理,具体代理其他干活的那些参数解析器。
其中最重要的就是前两种。
参数解析器介绍
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 注解的参数。
SessionAttributeMethodArgumentResolver:这个用来处理使用了@SessionAttribute 注解的参数。
MatrixVariableMethodArgumentResolver:这个处理使用了@MatrixVariable 注解并且参数类型不是 Map 的参数,如果参数类型是 Map,则使用MatrixVariableMapMethodArgumentResolver 来处理。
MatrixVariableMapMethodArgumentResolver:见上。
ExpressionValueMethodArgumentResolver:这个用来处理使用了@Value 注解的参数。
ServletResponseMethodArgumentResolver:这个用来处理 ServletResponse、OutputStream 以及 Writer 类型的参数。
ModelMethodProcessor:这个用来处理 Model 类型参数,并返回 model。
ModelAttributeMethodProcessor:这个用来处理使用了@ModelAttribute 注解的参数。
ServletModelAttributeMethodProcessor:这个用来处理使用了@ModelAttribute 注解的参数。
SessionStatusMethodArgumentResolver:这个用来处理 SessionStatus 类型的参数。
PrincipalMethodArgumentResolver:这个用来处理 Principal 类型的参数。
ContinuationHandlerMethodArgumentResolver:参数类型为 kotlin.coroutines.Continuation 的无操作解析器。
AbstractMessageConverterMethodArgumentResolver:这是一个父类,当使用 HttpMessageConverter 解析 requestbody 类型参数时,相关的处理类都会继承自它。
RequestPartMethodArgumentResolver:这个用来处理使用了@RequestPart 注解、MultipartFile 以及 Part 类型的参数。
AbstractMessageConverterMethodProcessor:这是一个工具类,不承担参数解析任务。
RequestResponseBodyMethodProcessor:这个用来处理添加了@RequestBody 注解的参数。
HttpEntityMethodProcessor:这个用来处理 HttpEntity 和 RequestEntity 类型的参数。
AbstractWebArgumentResolverAdapter:这种不做参数解析,仅仅用来作为 WebArgumentResolver 类型的参数解析器的适配器。
ServletWebArgumentResolverAdapter:这个给父类提供 request。
UriComponentsBuilderMethodArgumentResolver:这个用来处理 UriComponentsBuilder 类型的参数。
ServletRequestMethodArgumentResolver:这个用来处理 WebRequest、ServletRequest、MultipartRequest、HttpSession、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId 类型的参数。
HandlerMethodArgumentResolverComposite:这是一个组合解析器,它是一个代理,具体代理其他干活的那些参数解析器。
RedirectAttributesMethodArgumentResolver:这个用来处理 RedirectAttributes 类型的参数。
AbstractNamedValueMethodArgumentResolver
AbstractNamedValueMethodArgumentResolver 是一个抽象类,一些键值对类型的参数解析器都是通过继承它实现的,它里边定义了很多这些键值对类型参数解析器的公共操作。其中也是应用了很多模版模式,例如它没有实现 supportsParameter() 方法,该方法的具体实现在不同的子类中,不过它实现了 resolveArgument() 方法:
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
NamedValueInfo namedValueInfo = this.getNamedValueInfo(parameter);
MethodParameter nestedParameter = parameter.nestedIfOptional();
Object resolvedName = this.resolveEmbeddedValuesAndExpressions(namedValueInfo.name);
if (resolvedName == null) {
throw new IllegalArgumentException("Specified name must not resolve to null: [" + namedValueInfo.name + "]");
} else {
Object arg = this.resolveName(resolvedName.toString(), nestedParameter, webRequest);
if (arg == null) {
if (namedValueInfo.defaultValue != null) {
arg = this.resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
} else if (namedValueInfo.required && !nestedParameter.isOptional()) {
this.handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}
arg = this.handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
} else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
arg = this.resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
}
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, (Object)null, namedValueInfo.name);
try {
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
} catch (ConversionNotSupportedException var11) {
throw new MethodArgumentConversionNotSupportedException(arg, var11.getRequiredType(), namedValueInfo.name, parameter, var11.getCause());
} catch (TypeMismatchException var12) {
throw new MethodArgumentTypeMismatchException(arg, var12.getRequiredType(), namedValueInfo.name, parameter, var12.getCause());
}
if (arg == null && namedValueInfo.defaultValue == null && namedValueInfo.required && !nestedParameter.isOptional()) {
this.handleMissingValueAfterConversion(namedValueInfo.name, nestedParameter, webRequest);
}
}
this.handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
return arg;
}
}
根据代码可以看到,resolveArgument() 方法的流程如下:
首先根据当前请求获取一个 NamedValueInfo 对象,这个对象中保存了参数的三个属性:参数名、参数是否必须以及参数默认值。具体的获取过程就是先去缓存中拿,缓存中如果有,就直接返回,缓存中如果没有,则调用 createNamedValueInfo() 方法去创建,将创建结果缓存起来并返回。createNamedValueInfo() 方法是一个模版方法,具体的实现在子类中。
接下来处理 Optional 类型参数。
resolveEmbeddedValuesAndExpressions() 方法是为了处理注解中使用了 SpEL 表达式的情况,例如如下接口:
@GetMapping("/hello2") public void hello2(@RequestParam(value = "${aa.bb}") String name) { System.out.println("name = " + name); }
参数名使用了表达式,那么 resolveEmbeddedValuesAndExpressions() 方法的目的就是解析出表达式的值,如果没用到表达式,那么该方法会将原参数原封不动返回。
接下来调用 resolveName() 方法解析出参数的具体值,这个方法也是一个模版方法,具体的实现在子类中。
如果获取到的参数值为 null,先去看注解中有没有默认值,然后再去看参数值是否是必须的,如果是,则抛异常出来,否则就设置为 null 即可。
如果解析出来的参数值为空字符串 "",则也去 resolveEmbeddedValuesAndExpressions() 方法中走一遭。
最后则是 WebDataBinder 的处理,解决一些全局参数的问题。
在这个流程中,可以看到主要有两个方法是在子类中实现的,分别是 createNamedValueInfo() 方法和 resolveName() 方法,再加上 supportsParameter() 方法,子类中一共有三个方法需要实现。下面以 RequestParamMethodArgumentResolver 为例,来看下这三个方法。
RequestParamMethodArgumentResolver
supportsParameter
public boolean supportsParameter(MethodParameter parameter) {
if (parameter.hasParameterAnnotation(RequestParam.class)) {
if (!Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
return true;
} else {
RequestParam requestParam = (RequestParam)parameter.getParameterAnnotation(RequestParam.class);
return requestParam != null && StringUtils.hasText(requestParam.name());
}
} else if (parameter.hasParameterAnnotation(RequestPart.class)) {
return false;
} else {
parameter = parameter.nestedIfOptional();
if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
return true;
} else {
return this.useDefaultResolution ? BeanUtils.isSimpleProperty(parameter.getNestedParameterType()) : false;
}
}
}
从 supportsParameter() 方法中可以非常方便的看出支持的参数类型:
首先参数如果有@RequestParam 注解的话,则分两种情况:参数类型如果是 Map,则@RequestParam 注解必须配置 name 属性,否则不支持;如果参数类型不是 Map,则支持。
参数如果含有@RequestPart 注解,则不支持。
检查下是不是文件上传请求,如果是,则支持。
如果前面都没能返回,则使用默认的解决方案,调用BeanUtils.isSimpleProperty() 方法判断是不是简单类型,主要就是 Void、枚举、字符串、数字、日期等等。
createNamedValueInfo
protected AbstractNamedValueMethodArgumentResolver.NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
RequestParam ann = (RequestParam)parameter.getParameterAnnotation(RequestParam.class);
return ann != null ? new RequestParamNamedValueInfo(ann) : new RequestParamNamedValueInfo();
}
获取注解,读取注解中的属性,构造 RequestParamNamedValueInfo 对象返回。
resolveName
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
HttpServletRequest servletRequest = (HttpServletRequest)request.getNativeRequest(HttpServletRequest.class);
Object arg;
if (servletRequest != null) {
arg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
if (arg != MultipartResolutionDelegate.UNRESOLVABLE) {
return arg;
}
}
arg = null;
MultipartRequest 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;
}
根据代码可以看到,resolveName() 方法的逻辑比较简单:
前面两个 if 主要是为了处理文件上传请求。
如果不是文件上传请求,则调用request.getParameterValues() 方法取出参数返回。