文章目录
了解HandlerMethodArgumentResolver
HandlerMethodArgumentResolver,顾名思义,handler方法参数解析,它是如何帮助我们实现参数的解析过程的呢?我们先来看一下它定义的接口。
public interface HandlerMethodArgumentResolver {
// 是否支持参数解析
boolean supportsParameter(MethodParameter parameter);
/**
* 参数解析,其实如何仅仅只是解析的话,不需要这么多参数,解析完成后还需要绑定校验,WebDataBinderFactory
* 帮我们完成
*/
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
可用的HandlerMethodArgumentResolver实现
- 基于注解的参数解析器
- RequestHeaderMethodArgumentResolver:解析@RequestHeader注解获取请求头中的值,参数类型不支持Map。
- ServletCookieValueMethodArgumentResolver:解析@CookieValue获取cookie中的值。
- RequestAttributeMethodArgumentResolver:解析@RequestAttribute从request域中获取值。
- SessionAttributeMethodArgumentResolver:解析@SessionAttribute从session域中获取值。
- RequestParamMethodArgumentResolver:解析@RequestParam注解。
/**
*这个参数解析器有两种模式,useDefaultResolution为true时范围更广,包含一些简单类型,即使没有添加注解
* @RequestParam,这些类型包括:基本类型和它们的包装类以及数组,Enum,CharSequence,Number,Date,URI
* Locale和它们的子类,MultipartFile,Part和它们的集合或数组(前提是不要使用@RequestPart);使用
* @RequestParam注解时,参数类型支持Map,但要求注解name属性必须设置。
*/
public RequestParamMethodArgumentResolver(@Nullable ConfigurableBeanFactory beanFactory,
boolean useDefaultResolution) {
super(beanFactory);
this.useDefaultResolution = useDefaultResolution;
}
- ExpressionValueMethodArgumentResolver:解析@Value注解。
- MatrixVariableMethodArgumentResolver:解析@MatrixVariable注解,参数类型支持Map,但要求注解name属性必须设置。
- PathVariableMethodArgumentResolver:解析@PathVariable注解,参数类型支持Map,但要求注解name属性必须设置。
至于将这些参数解析器放在一起介绍,是因为它们有一个共同的父类,AbstractNamedValueMethod-ArgumentResolver,而参数解析的主要逻辑就在这里。
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
// 根据参数的注解构建NamedValueInfo信息,NamedValueInfo包含3个字段,分别是name,required,defaultValue
// 我们可以发现大多数注解可以指定这几个字段,如果name为空的话,将会取parameterName作为name
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
MethodParameter nestedParameter = parameter.nestedIfOptional();
// 解析参数名,name中可能包含占位符或表达式
Object resolvedName = resolveStringValue(namedValueInfo.name);
if (resolvedName == null) {
throw new IllegalArgumentException(
"Specified name must not resolve to null: [" + namedValueInfo.name + "]");
}
// 根据参数名解析参数值
Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
if (arg == null) {
if (namedValueInfo.defaultValue != null) {
// 参数值为空取默认值
arg = resolveStringValue(namedValueInfo.defaultValue);
}else if (namedValueInfo.required && !nestedParameter.isOptional()) {
// 参数值为空,要求又必须要有,这里根据不同参数解析器抛出不同异常
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}
// 如果参数类型为boolean类型返回false,其他基本类型会报错
arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
}else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
arg = resolveStringValue(namedValueInfo.defaultValue);
}
// 参数绑定校验 以及参数解析完成后置处理
...
}
- ①RequestParamMapMethodArgumentResolver
- ②PathVariableMapMethodArgumentResolver
- ③MatrixVariableMapMethodArgumentResolver
- ④RequestHeaderMapMethodArgumentResolver
①②③④可以看出来和上面某些参数解析器名称类似,只是名称中多了"Map",它们的功能也就是当请求参数为Map时,进行参数解析,其实①②③对应没有"Map"名称的参数解析器也支持参数类型Map,它们的区别就是是否要求注解属性name有值。①②③④只需要添加对应注解,参数为Map类型,无需配置注解name值。
- RequestPartMethodArgumentResolver:解析@RequestPart注解,当既没有@RequestPart,也没有@RequestParam时,也支持参数类型为MultipartFile,Part和它们的集合或数组。
- RequestResponseBodyMethodProcessor:请求参数解析时处理@RequestBody注解,返回值解析@ResponseBody注解。
这两个类有共同的父类,AbstractMessageConverterMethodArgumentResolver,主要负责读取请求信息进行转换。我们可以在对象转换前后进行一些自定义的处理,这里大家可以查看我的另一篇博客,springMVC之RequestBodyAdvice和ResponseBodyAdvice。
- ModelAttributeMethodProcessor
// 这个参数解析器也支持两种模式,当annotationNotRequired设置为false时,解析@ModelAttribute注解,当为true时
// 还支持引用类型
public ModelAttributeMethodProcessor(boolean annotationNotRequired) {
this.annotationNotRequired = annotationNotRequired;
}
public boolean supportsParameter(MethodParameter parameter) {
return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
(this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
}
- 基于类型的参数解析器
- ServletRequestMethodArgumentResolver
public boolean supportsParameter(MethodParameter parameter) {
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) ||
InputStream.class.isAssignableFrom(paramType) ||
Reader.class.isAssignableFrom(paramType) ||
HttpMethod.class == paramType ||
Locale.class == paramType ||
TimeZone.class == paramType ||
ZoneId.class == paramType);
}
- ServletResponseMethodArgumentResolver
public boolean supportsParameter(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType();
return (ServletResponse.class.isAssignableFrom(paramType) ||
OutputStream.class.isAssignableFrom(paramType) ||
Writer.class.isAssignableFrom(paramType));
}
- HttpEntityMethodProcessor
public boolean supportsParameter(MethodParameter parameter) {
return (HttpEntity.class == parameter.getParameterType() ||
RequestEntity.class == parameter.getParameterType());
}
- RedirectAttributesMethodArgumentResolver
// RedirectAttributes.addFlashAttribute方法可以帮助我们在重定向的请求中发送数据
public boolean supportsParameter(MethodParameter parameter) {
return RedirectAttributes.class.isAssignableFrom(parameter.getParameterType());
}
- ModelMethodProcessor
public boolean supportsParameter(MethodParameter parameter) {
return Model.class.isAssignableFrom(parameter.getParameterType());
}
- MapMethodProcessor
public boolean supportsParameter(MethodParameter parameter) {
return Map.class.isAssignableFrom(parameter.getParameterType());
}
- ErrorsMethodArgumentResolver
public boolean supportsParameter(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType();
return Errors.class.isAssignableFrom(paramType);
}
public Object resolveArgument(MethodParameter parameter,
@Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
@Nullable WebDataBinderFactory binderFactory) throws Exception {
ModelMap model = mavContainer.getModel();
// 参数校验后会将BindingResult放到model中,这里获取model中最后一个key,要确保
// 拿到的就是BindingResult,那么请求参数Errors要紧跟着校验参数之后
String lastKey = CollectionUtils.lastElement(model.keySet());
if (lastKey != null && lastKey.startsWith(BindingResult.MODEL_KEY_PREFIX)) {
return model.get(lastKey);
}
throw new IllegalStateException(
"An Errors/BindingResult argument is expected to be declared immediately after " +
"the model attribute, the @RequestBody or the @RequestPart arguments " +
"to which they apply: " + parameter.getMethod());
}
- SessionStatusMethodArgumentResolver
public boolean supportsParameter(MethodParameter parameter) {
return SessionStatus.class == parameter.getParameterType();
}
- UriComponentsBuilderMethodArgumentResolver
public boolean supportsParameter(MethodParameter parameter) {
Class<?> type = parameter.getParameterType();
return (UriComponentsBuilder.class == type || ServletUriComponentsBuilder.class == type);
}
像上面提到的所有参数解析器中支持的类型以及注解,我们都可以在编写请求处理方法时,使用它们。针对每一个参数解析器的具体逻辑,这里就不贴源码展示了,大家感兴趣可以自己去阅读源码。
可用的HandlerMethodReturnValueHandler实现
根据返回值类型适用目的来划分的话,有单一目的,也有多个目的的,举个例子,返回值为String类型,有可能表示视图信息,也有可能是返回的数据,也有可能是想添加到model域中,这个时候解析器的顺序就很重要了,返回值适用多种目的应该放到最后。
- 返回值类型适用于单一目的
- ModelAndViewMethodReturnValueHandler:处理ModelAndView类型
- ModelMethodProcessor:处理Model类型
- ViewMethodReturnValueHandler:处理View类型
- HttpEntityMethodProcessor
public boolean supportsReturnType(MethodParameter returnType) {
return (HttpEntity.class.isAssignableFrom(returnType.getParameterType()) &&
!RequestEntity.class.isAssignableFrom(returnType.getParameterType()));
}
- HttpHeadersReturnValueHandler:处理HttpHeaders类型
- ResponseBodyEmitterReturnValueHandler
- StreamingResponseBodyReturnValueHandler
- CallableMethodReturnValueHandler
- DeferredResultMethodReturnValueHandler
- AsyncTaskMethodReturnValueHandler
上面标粗的部分支持异步处理,将在单独的博客中详细介绍。
springMVC请求异步处理之(DeferredResultMethod,ResponseBodyEmitter)ReturnValueHandler
springMVC请求异步处理之(StreamingResponseBody,CallableMethod,AsyncTaskMethod)
ReturnValueHandler
- 使用@ResponseBody或@ModelAttribute注解
- ModelAttributeMethodProcessor
// 返回值添加@ModelAttribute注解或当annotationNotRequired为true时
// BeanUtils.isSimpleProperty(type)为false
public boolean supportsReturnType(MethodParameter returnType) {
return (returnType.hasMethodAnnotation(ModelAttribute.class) ||
// annotationNotRequired 设置为true时,不需要注解
(this.annotationNotRequired && !BeanUtils.isSimpleProperty(returnType.getParameterType())));
}
- RequestResponseBodyMethodProcessor
// 方法或返回值上添加了@ResponseBody注解
public boolean supportsReturnType(MethodParameter returnType) {
return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
returnType.hasMethodAnnotation(ResponseBody.class));
}
- 返回值类型适用于多种目的
- ViewNameMethodReturnValueHandler:处理String或void
- MapMethodProcessor:处理Map类型
对具体处理逻辑感兴趣的同学可以自己去阅读源码,这里就不贴源码了。
它们在哪里被调用
- RequestMappingHandlerAdapter初始化的时候,如果没有设置HandlerMethodArgumentResolver,Hand-lerMethodReturnValueHandler,将会使用默认,包含上面介绍的所有。如果想要设置自定义的解析器的话,建议通过调用它的setCustomArgumentResolvers或setCustomReturnValueHandlers方法。
- RequestMappingHandlerAdapter处理请求时,最终会调用InvocableHandlerMethod的invokeAndHandle方法,在这过程中会进行参数以及返回值解析。
- 参数解析最初交给HandlerMethodArgumentResolverComposite来处理,这就是打了包的参数解析器,它会帮助我们找到对应的参数解析器进行处理。返回值也同理,它是交给HandlerMethodReturnValueHandler-Composite来处理。
// RequestMappingHandlerAdapter初始化的时候会加载
public void afterPropertiesSet() {
//...
// 如果未设置,将使用默认的参数解析器
if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
//...
// 如果未设置,将使用默认的返回值解析器
if (this.returnValueHandlers == null) {
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
}
ServletInvocableHandlerMethod:
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 这里调用InvocableHandlerMethod#invokeForRequest方法,这里将会进行参数解析
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
// 返回值为void
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
// 如果使用了ResponseStatus注解,并且声明了reason属性的话,到这里就结束了
}else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
try {
// 返回值处理
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}catch (Exception ex) {
// 异常处理 ...
}
}
// 请求参数解析
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
MethodParameter[] parameters = getMethodParameters();
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
}
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
// 初始化此方法参数的参数名称发现。当方法中调用getParameterName方法依赖的就是parameterNameDiscoverer
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
// 获取请求参数
args[i] = findProvidedArgument(parameter, providedArgs);
// 说明已经被解析过了
if (args[i] != null) {
continue;
}
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
// 找到支持此参数的解析器进行解析
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}catch (Exception ex) {
//...
throw ex;
}
}
return args;
}
小结
- 每一个请求参数或返回值上的注解都有对应的解析器进行解析。
- RequestParamMethodArgumentResolver和ModelAttributeMethodProcessor构造器有一个参数可以控制解析行为,当设置为true时,不需要添加注解,也可以进行解析,像引用类型就是交给ModelAttributeMethod-Processor来处理,而一些简单的类型(包含基本类型)交给RequestParamMethodArgumentResolver来处理。而Map以及一些特殊类型都能找到所属解析器。
- 返回值处理器配置时要注意顺序,避免解析不到或者无用解析。这个一般使用时都已经帮我们配置好了,所以我们可以不用关注,只需了解即可。