springMVC请求参数以及返回值解析

了解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实现
  1. 基于注解的参数解析器
    在这里插入图片描述
  • 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())));
}
  1. 基于类型的参数解析器
  • 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域中,这个时候解析器的顺序就很重要了,返回值适用多种目的应该放到最后。

  1. 返回值类型适用于单一目的
  • 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

  1. 使用@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));
}
  1. 返回值类型适用于多种目的
  • ViewNameMethodReturnValueHandler:处理String或void
  • MapMethodProcessor:处理Map类型

对具体处理逻辑感兴趣的同学可以自己去阅读源码,这里就不贴源码了。

它们在哪里被调用
  1. RequestMappingHandlerAdapter初始化的时候,如果没有设置HandlerMethodArgumentResolver,Hand-lerMethodReturnValueHandler,将会使用默认,包含上面介绍的所有。如果想要设置自定义的解析器的话,建议通过调用它的setCustomArgumentResolvers或setCustomReturnValueHandlers方法。
  2. RequestMappingHandlerAdapter处理请求时,最终会调用InvocableHandlerMethod的invokeAndHandle方法,在这过程中会进行参数以及返回值解析。
  3. 参数解析最初交给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;
	}
小结
  1. 每一个请求参数或返回值上的注解都有对应的解析器进行解析。
  2. RequestParamMethodArgumentResolver和ModelAttributeMethodProcessor构造器有一个参数可以控制解析行为,当设置为true时,不需要添加注解,也可以进行解析,像引用类型就是交给ModelAttributeMethod-Processor来处理,而一些简单的类型(包含基本类型)交给RequestParamMethodArgumentResolver来处理。而Map以及一些特殊类型都能找到所属解析器。
  3. 返回值处理器配置时要注意顺序,避免解析不到或者无用解析。这个一般使用时都已经帮我们配置好了,所以我们可以不用关注,只需了解即可。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值