小架构step系列21:参数和返回值的匹配
1 概述
从前面看到参数是通过HandlerMethodArgumentResolver进行匹配的,参数有各种各样的形式,比如带注解的、自定义类、基本类型、甚至ServletReqest之类的。返回值则是通过ReturnValueHandler来处理的,返回值也有比较多类型,比如String、Void、Model、自定义类等,如果不是前后端分离的,还可能带View之类的。本文了解一下参数和返回值的匹配原理。
2 参数匹配
2.1 参数匹配原理
遍历所有HandlerMethodArgumentResolver,如果Resolver的supportsParameter()接口返回值为true,则匹配此Resolver,不再看下面其它的Resolver。
// 源码位置:org.springframework.web.method.support.HandlerMethodArgumentResolverComposite
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);if (result == null) {// 遍历所有argumentResolver,调用其supportsParameter()方法,如果返回值为true则匹配上此argumentResolverfor (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {if (resolver.supportsParameter(parameter)) {result = resolver;this.argumentResolverCache.put(parameter, result);break;}}}return result;
}
默认有27个Resolver:
// 源码位置: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:
// 源码位置: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。
// 源码位置: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,则选中此ReturnValueHandlerif (handler.supportsReturnType(returnType)) {return handler;}}return null;
}
默认提供了15个ReturnValueHandler:
// 源码位置: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:
// 源码位置:org.springframework.web.servlet.mvc.method.annotation.ModelAndViewMethodReturnValueHandler
public boolean supportsReturnType(MethodParameter returnType) {// 返回值类型为ModelAndView,用此ReturnValueHandlerreturn ModelAndView.class.isAssignableFrom(returnType.getParameterType());
}// 源码位置:org.springframework.web.method.annotation.ModelMethodProcessor
public boolean supportsParameter(MethodParameter parameter) {// 返回值类型为Model,用此ReturnValueHandlerreturn Model.class.isAssignableFrom(parameter.getParameterType());
}// 源码位置:org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor
public boolean supportsReturnType(MethodParameter returnType) {// 返回值对象对应的类指定了@ResponseBody注解,或者返回值有指定@ResponseBody注解,用此ReturnValueHandlerreturn (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||returnType.hasMethodAnnotation(ResponseBody.class));
}// 源码位置:org.springframework.web.servlet.mvc.method.annotation.ViewMethodReturnValueHandler
public boolean supportsReturnType(MethodParameter returnType) {// 返回值类型为View,用此ReturnValueHandlerreturn View.class.isAssignableFrom(returnType.getParameterType());
}// 源码位置:org.springframework.web.servlet.mvc.method.annotation.ViewNameMethodReturnValueHandler
public boolean supportsReturnType(MethodParameter returnType) {Class<?> paramType = returnType.getParameterType();// 如果没有返回值或者返回值是字符串类型,则用此ReturnValueHandlerreturn (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=truereturn (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格式数据的适配;
方案二:所有参数都明确注解;好处是注解清晰不用自定义,缺点是容易漏标注解;