1 概述
从前面看到参数是通过HandlerMethodArgumentResolver进行匹配的,参数有各种各样的形式,比如带注解的、自定义类、基本类型、甚至ServletReqest之类的。返回值则是通过ReturnValueHandler来处理的,返回值也有比较多类型,比如String、Void、Model、自定义类等,如果不是前后端分离的,还可能带View之类的。本文了解一下参数和返回值的匹配原理。
2 参数匹配
2.1 参数匹配原理
遍历所有HandlerMethodArgumentResolver,如果Resolver的supportsParameter()接口返回值为true,则匹配此Resolver,不再看下面其它的Resolver。
java
// 源码位置:org.springframework.web.method.support.HandlerMethodArgumentResolverComposite
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
if (result == null) {
// 遍历所有argumentResolver,调用其supportsParameter()方法,如果返回值为true则匹配上此argumentResolver
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
if (resolver.supportsParameter(parameter)) {
result = resolver;
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}
默认有27个Resolver:
java
// 源码位置:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(30);
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
resolvers.add(new RequestParamMapMethodArgumentResolver());
resolvers.add(new PathVariableMethodArgumentResolver());
resolvers.add(new PathVariableMapMethodArgumentResolver());
resolvers.add(new MatrixVariableMethodArgumentResolver());
resolvers.add(new MatrixVariableMapMethodArgumentResolver());
resolvers.add(new ServletModelAttributeMethodProcessor(false));
resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
resolvers.add(new RequestHeaderMapMethodArgumentResolver());
resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new SessionAttributeMethodArgumentResolver());
resolvers.add(new RequestAttributeMethodArgumentResolver());
resolvers.add(new ServletRequestMethodArgumentResolver());
resolvers.add(new ServletResponseMethodArgumentResolver());
resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RedirectAttributesMethodArgumentResolver());
resolvers.add(new ModelMethodProcessor());
resolvers.add(new MapMethodProcessor());
resolvers.add(new ErrorsMethodArgumentResolver());
resolvers.add(new SessionStatusMethodArgumentResolver());
resolvers.add(new UriComponentsBuilderMethodArgumentResolver());
if (KotlinDetector.isKotlinPresent()) {
resolvers.add(new ContinuationHandlerMethodArgumentResolver());
}
// 注意自定义的ArgumentResolver位置
if (getCustomArgumentResolvers() != null) {
resolvers.addAll(getCustomArgumentResolvers());
}
resolvers.add(new PrincipalMethodArgumentResolver());
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
resolvers.add(new ServletModelAttributeMethodProcessor(true));
return resolvers;
}
org.springframework.web.method.annotation.RequestParamMethodArgumentResolver
org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.PathVariableMapMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMapMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor
org.springframework.web.servlet.mvc.method.annotation.RequestPartMethodArgumentResolver
org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver
org.springframework.web.method.annotation.RequestHeaderMapMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.ServletCookieValueMethodArgumentResolver
org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.SessionAttributeMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.RequestAttributeMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.ServletResponseMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor
org.springframework.web.servlet.mvc.method.annotation.RedirectAttributesMethodArgumentResolver
org.springframework.web.method.annotation.ModelMethodProcessor
org.springframework.web.method.annotation.MapMethodProcessor
org.springframework.web.method.annotation.ErrorsMethodArgumentResolver
org.springframework.web.method.annotation.SessionStatusMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.UriComponentsBuilderMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.PrincipalMethodArgumentResolver
org.springframework.web.method.annotation.RequestParamMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor
注意:RequestParamMethodArgumentResolver和ServletModelAttributeMethodProcessor有两个。
2.2 常用的MethodArgumentResolver
下面列一下几个常用的MethodArgumentResolver:
java
// 源码位置:org.springframework.web.method.annotation.RequestParamMethodArgumentResolver
public boolean supportsParameter(MethodParameter parameter) {
// 参数前加了@RequestParam注解时,用此MethodArgumentResolver处理
if (parameter.hasParameterAnnotation(RequestParam.class)) {
// 如果参数是Map类型,则还需要在@RequestParam注解中指定映射字段名
if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
return (requestParam != null && StringUtils.hasText(requestParam.name()));
}
else {
return true;
}
}
else {
// 参数前加了@RequestPart注解时,不用此MethodArgumentResolver处理
if (parameter.hasParameterAnnotation(RequestPart.class)) {
return false;
}
parameter = parameter.nestedIfOptional();
// 参数类型为MultipartFile或Part时(可以List里放这些类型),用此MethodArgumentResolver处理
if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
return true;
}
else if (this.useDefaultResolution) {
// SpringMVC准备了两个RequestParamMethodArgumentResolver,一个useDefaultResolution=false,另外一个useDefaultResolution=true(倒数第二个Resolver)
// 如果useDefaultResolution=true,则普通类型如Void、int/byte/long等基础类型可以用此MethodArgumentResolver处理
// 详细类型参考BeanUtils.isSimpleProperty()方法
return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
}
else {
return false;
}
}
}
// 源码位置:org.springframework.beans.BeanUtils
public static boolean isSimpleValueType(Class<?> type) {
return (Void.class != type && void.class != type &&
(ClassUtils.isPrimitiveOrWrapper(type) ||
Enum.class.isAssignableFrom(type) ||
CharSequence.class.isAssignableFrom(type) ||
Number.class.isAssignableFrom(type) ||
Date.class.isAssignableFrom(type) ||
Temporal.class.isAssignableFrom(type) ||
URI.class == type ||
URL.class == type ||
Locale.class == type ||
Class.class == type));
}
// 源码位置:org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver
public boolean supportsParameter(MethodParameter parameter) {
// 参数前加了@RequestParam注解、且参数类型为Map类型、也没有在@RequestParam注解指定字段映射名时,用此MethodArgumentResolver处理
RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
return (requestParam != null && Map.class.isAssignableFrom(parameter.getParameterType()) &&
!StringUtils.hasText(requestParam.name()));
}
// 源码位置:org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver
public boolean supportsParameter(MethodParameter parameter) {
// 参数前没有加@PathVariable注解,不用此MethodArgumentResolver处理
if (!parameter.hasParameterAnnotation(PathVariable.class)) {
return false;
}
// 参数前加了@PathVariable注解,且参数为Map类型时,需要在注解指定字段映射名,才用此MethodArgumentResolver处理
if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
PathVariable pathVariable = parameter.getParameterAnnotation(PathVariable.class);
return (pathVariable != null && StringUtils.hasText(pathVariable.value()));
}
return true;
}
// 源码位置:org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#supportsParameter
public boolean supportsParameter(MethodParameter parameter) {
// 参数前加了@RequestBody注解,用此MethodArgumentResolver处理
return parameter.hasParameterAnnotation(RequestBody.class);
}
// 源码位置:org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor
// 实际由父类org.springframework.web.method.annotation.ModelAttributeMethodProcessor提供supportsParameter()方法
public boolean supportsParameter(MethodParameter parameter) {
// 两种情况用此MethodArgumentResolver处理:
// 1. 参数前加了@ModelAttribute注解;
// 2. 没有加参数注解,参数未非普通类型(普通类型参考上面BeanUtils.isSimpleProperty()的实现);
// 注:有两个ServletModelAttributeMethodProcessor,一个annotationNotRequired=false,另一个annotationNotRequired=true(倒数第一个Resolver)
return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
(this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
}
// 源码位置:org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver
public boolean supportsParameter(MethodParameter parameter) {
// 参数是Servlet有关的类型,用此MethodArgumentResolver处理
Class<?> paramType = parameter.getParameterType();
return (WebRequest.class.isAssignableFrom(paramType) ||
ServletRequest.class.isAssignableFrom(paramType) ||
MultipartRequest.class.isAssignableFrom(paramType) ||
HttpSession.class.isAssignableFrom(paramType) ||
(pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
(Principal.class.isAssignableFrom(paramType) && !parameter.hasParameterAnnotations()) ||
InputStream.class.isAssignableFrom(paramType) ||
Reader.class.isAssignableFrom(paramType) ||
HttpMethod.class == paramType ||
Locale.class == paramType ||
TimeZone.class == paramType ||
ZoneId.class == paramType);
}
从上面可以看到如果有注解则优先匹配注解(如@RequestParam、@RequestPart、@RequestBody)。
在没有注解的时候,主要由RequestParamMethodArgumentResolver、ServletModelAttributeMethodProcessor、ServletRequestMethodArgumentResolver、自定义Resolver处理。
- 在Resolver列表的排序是:ServletRequestMethodArgumentResolver、自定义Resolver、RequestParamMethodArgumentResolver、ServletModelAttributeMethodProcessor。
- RequestParamMethodArgumentResolver和ServletModelAttributeMethodProcessor有两个,它们各自的第二个都置标记为true,才会处理没有注解的情况。
- ServletRequestMethodArgumentResolver匹配的是Servlet有关的类型,如ServletRequest、InputStream等;
- RequestParamMethodArgumentResolver匹配的是普通类型,如Void、Number、int/String基本类型等;
- ServletModelAttributeMethodProcessor匹配的是非普通类型,也就是上面没匹配到的所有类型;
2.3 应用
在请求的时候,一般请求参数有两种形式:
- Form表单形式:一个字段一个字段分开的方式,字段可以是对象结构,大部分ArgumentResolver都是用来匹配这种形式的。
- JSON数据形式:所有数据封装到一个JSON对象当中,要匹配这种形式的参数,参数前必须加@RequestBody注解,由RequestResponseBodyMethodProcessor匹配。
如果强制请求参数用JSON数据形式,又不希望必须在参数前加@RequestBody注解(可能忘掉),则需要自行加一个自定义的ArgumentResolver,把所有情况都当JSON数据处理。
3 返回值匹配
3.1 返回值匹配原理
遍历所有ReturnValueHandler,如果Resolver的supportsReturnType()接口返回值为true,则匹配此Handler,不再看下面其它的Handler。
java
// 源码位置:org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
boolean isAsyncValue = isAsyncReturnValue(value, returnType);
for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
continue;
}
// 遍历所有支持的ReturnValueHandler,调用supportsReturnType()接口,如果返回值为true,则选中此ReturnValueHandler
if (handler.supportsReturnType(returnType)) {
return handler;
}
}
return null;
}
默认提供了15个ReturnValueHandler:
java
// 源码位置:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>(20);
handlers.add(new ModelAndViewMethodReturnValueHandler());
handlers.add(new ModelMethodProcessor());
handlers.add(new ViewMethodReturnValueHandler());
handlers.add(new ResponseBodyEmitterReturnValueHandler(getMessageConverters(),
this.reactiveAdapterRegistry, this.taskExecutor, this.contentNegotiationManager));
handlers.add(new StreamingResponseBodyReturnValueHandler());
handlers.add(new HttpEntityMethodProcessor(getMessageConverters(),
this.contentNegotiationManager, this.requestResponseBodyAdvice));
handlers.add(new HttpHeadersReturnValueHandler());
handlers.add(new CallableMethodReturnValueHandler());
handlers.add(new DeferredResultMethodReturnValueHandler());
handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory));
handlers.add(new ServletModelAttributeMethodProcessor(false));
handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(),
this.contentNegotiationManager, this.requestResponseBodyAdvice));
handlers.add(new ViewNameMethodReturnValueHandler());
handlers.add(new MapMethodProcessor());
// 注意自定义的ReturnValueHandler的位置
if (getCustomReturnValueHandlers() != null) {
handlers.addAll(getCustomReturnValueHandlers());
}
if (!CollectionUtils.isEmpty(getModelAndViewResolvers())) {
handlers.add(new ModelAndViewResolverMethodReturnValueHandler(getModelAndViewResolvers()));
}
else {
handlers.add(new ServletModelAttributeMethodProcessor(true));
}
return handlers;
}
org.springframework.web.servlet.mvc.method.annotation.ModelAndViewMethodReturnValueHandler
org.springframework.web.method.annotation.ModelMethodProcessor
org.springframework.web.servlet.mvc.method.annotation.ViewMethodReturnValueHandler
org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitterReturnValueHandler
org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBodyReturnValueHandler
org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor
org.springframework.web.servlet.mvc.method.annotation.HttpHeadersReturnValueHandler
org.springframework.web.servlet.mvc.method.annotation.CallableMethodReturnValueHandler
org.springframework.web.servlet.mvc.method.annotation.DeferredResultMethodReturnValueHandler
org.springframework.web.servlet.mvc.method.annotation.AsyncTaskMethodReturnValueHandler
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor
org.springframework.web.servlet.mvc.method.annotation.ViewNameMethodReturnValueHandler
org.springframework.web.method.annotation.MapMethodProcessor
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor
3.2 常用的ReturnValueHandler
下面列一下几个常用的ReturnValueHandler:
java
// 源码位置:org.springframework.web.servlet.mvc.method.annotation.ModelAndViewMethodReturnValueHandler
public boolean supportsReturnType(MethodParameter returnType) {
// 返回值类型为ModelAndView,用此ReturnValueHandler
return ModelAndView.class.isAssignableFrom(returnType.getParameterType());
}
// 源码位置:org.springframework.web.method.annotation.ModelMethodProcessor
public boolean supportsParameter(MethodParameter parameter) {
// 返回值类型为Model,用此ReturnValueHandler
return Model.class.isAssignableFrom(parameter.getParameterType());
}
// 源码位置:org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor
public boolean supportsReturnType(MethodParameter returnType) {
// 返回值对象对应的类指定了@ResponseBody注解,或者返回值有指定@ResponseBody注解,用此ReturnValueHandler
return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
returnType.hasMethodAnnotation(ResponseBody.class));
}
// 源码位置:org.springframework.web.servlet.mvc.method.annotation.ViewMethodReturnValueHandler
public boolean supportsReturnType(MethodParameter returnType) {
// 返回值类型为View,用此ReturnValueHandler
return View.class.isAssignableFrom(returnType.getParameterType());
}
// 源码位置:org.springframework.web.servlet.mvc.method.annotation.ViewNameMethodReturnValueHandler
public boolean supportsReturnType(MethodParameter returnType) {
Class<?> paramType = returnType.getParameterType();
// 如果没有返回值或者返回值是字符串类型,则用此ReturnValueHandler
return (void.class == paramType || CharSequence.class.isAssignableFrom(paramType));
}
// 源码位置:org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor
// 实际用父类org.springframework.web.method.annotation.ModelAttributeMethodProcessor的实现
public boolean supportsReturnType(MethodParameter returnType) {
// 返回值类型为ModelAttribute,或者在annotationNotRequired=true的时候不是普通类型
// ServletModelAttributeMethodProcessor有两个,第二个(在列表最后一个)annotationNotRequired=true
return (returnType.hasMethodAnnotation(ModelAttribute.class) ||
(this.annotationNotRequired && !BeanUtils.isSimpleProperty(returnType.getParameterType())));
}
ReturnValueHandler的匹配逻辑是比较清晰的,大部分都是按返回值类型,还有一种是按@ResponseBody、@ModelAttribute等注解。
- 对于普通类型只能匹配Void和CharSequence,其它的如Number、Enum等是匹配不到的,会抛IllegalArgumentException("Unknown return value type: ")异常。
- 如果是自定义的类型,则由ServletModelAttributeMethodProcessor来匹配,注意它也有两个,只有第二个才是匹配自定义类型的。
- 也可以自定义ReturnValueHandler来匹配其它类型,此时会排在第二个ServletModelAttributeMethodProcessor前面,其它Handler的后面。
3.3 应用
返回值一般有带视图View和纯数据的两种方式:
- 带视图View的方式是在服务器端渲染界面的方式,返回值类型指定为ModelAndView、View、字符串等类型;
- 纯数据一般用JSON格式,此时需要由ServletModelAttributeMethodProcessor处理,即指定@ModelAttribute注解或自定义类型;
4 架构一小步
在前后端分离的情况下,可以选择两种规范:
1、数据格式方面
方案一:参数和返回值都强制统一用JSON数据格式;好处是统一,缺点是需要自定义一下MethodArgumentResolver;
方案二:参数用Form表单格式,返回值用JSON数据格式;好处是不需要自定义MethodArgumentResolver,缺点是参数和返回值需要分开理解,处理方式不统一;
2、在使用注解方面
方案一:所有参数不用注解;好处是不会出现漏注解的情况,缺点是需要JSON格式数据的适配;
方案二:所有参数都明确注解;好处是注解清晰不用自定义,缺点是容易漏标注解;