HandlerMethodArgumentResolver源码分析

基于SpringBoot 2.2.0.RELEASE

先看下Spring对HandlerMethodArgumentResolver的接口定义

/**
*用于将请求上下文中的方法参数解析为参数值的策略接口
*/
public interface HandlerMethodArgumentResolver {

	/**是否MethodParamter是否能被该resolver解析器支持
	 * @param parameter 待检查的方法参数
	 * @return {@code true} 如果解析器支持提供出来的参数 返回true
	 * 否则返回false
	 */
	boolean supportsParameter(MethodParameter parameter);

	/**
	 * 将给定请求的方法参数解析为参数值
	 * @param parameter 要解析的方法参数。 
	 * 此参数必须先前已传递给{supportsParameter},该必须已返回{@code true}。
	 * 返回已解析的参数值,如果无法解析,则返回{@code null}
	 * @throws Exception 如果出错则抛出异常
	 */
	@Nullable
	Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;

}

因为SpringMVC/SpringBoot中controller中方法入参对应多中情况,如@RequestParam类型的参数、@PathVariable类型的参数、@RequestBody类型的参数等等吧。相信HandlerMethodArgumentResolver提供了不同的实现类来根据参数类型解析对应的参数。


下面是常见HandlerMethodArgumentResolver实现类以及能处理的参数类型

  • RequestParamMethodArgumentResolver

带有@RequestParam注解的参数;(与MultipartResolver结合使用的)参数类型是MultipartFile;(与Servlet 3.0 multipart requests结合使用的)参数类型是javax.servlet.http.Part的参数以及一些没有被@RequestParam修饰的基本数据类型,如int/long等。

  • RequestParamMapMethodArgumentResolver

带有@RequestParam注解的类型是Map的参数

  • PathVariableMethodArgumentResolver

带有@PathVaribale注解类型的参数

  • RequestResponseBodyMethodProcessor

借助HttpMessageConverter,这个类是来解析注解为@RequestBody的请求参数和注解为@ResponseBody的响应内容

下面以源码分析其中的一些类

  • RequestParamMethodArgumentResolver
    在这里插入图片描述
public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver
		implements UriComponentsContributor {
@Override
	public boolean supportsParameter(MethodParameter parameter) {
	    //如果参数有被@RequestParam注解标识
		if (parameter.hasParameterAnnotation(RequestParam.class)) {
		//如果方法参数的类型是Map
			if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
				RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
				//因为指定在@RequestParam注解中的name 是用来解析请求参数的value值的,
				//所以下面是判断name是否存在,如果存在 则可以使用该解析器
				return (requestParam != null && StringUtils.hasText(requestParam.name()));
			}
			else {
			//如果被注解标注的不是Map类型
				return true;
			}
		}
		else {
		//如果参数有被@RequestPart注解标识
			if (parameter.hasParameterAnnotation(RequestPart.class)) {
				return false;
			}
			parameter = parameter.nestedIfOptional();
			//如果请求参数类型是MultipartFile
			if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
				return true;
			}
			//如果可以使用默认解析方案
			else if (this.useDefaultResolution) {
				return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
			}
			else {
				return false;
			}
		}
	}
	//将给定的参数类型和值名称解析为参数值。
    @Override
	@Nullable
	protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
		HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);

		if (servletRequest != null) {
		//如果方法中的参数是MultipartFile类型,则解析它的数据值
			Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
			if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
				return mpArg;
			}
		}

		Object arg = null;
		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;
	}
}
  • PathVariableMethodArgumentResolver
    在这里插入图片描述
public class PathVariableMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver
		implements UriComponentsContributor {


	@Override
	public boolean supportsParameter(MethodParameter parameter) {
	//如果参数没有被@PathVariable标注
		if (!parameter.hasParameterAnnotation(PathVariable.class)) {
			return false;
		}
		//如果方法的参数类型是Map
		if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
			PathVariable pathVariable = parameter.getParameterAnnotation(PathVariable.class);
			//因为指定在@PathVariable注解中的value 是用来解析请求参数的value值的,
				//所以下面是判断value是否存在,如果存在 则可以使用该解析器
			return (pathVariable != null && StringUtils.hasText(pathVariable.value()));
		}
		return true;
	}

//AbstractNamedValueMethodArgumentResolver#resolveArgument会调用到
	@Override
	@SuppressWarnings("unchecked")
	protected void handleResolvedValue(@Nullable Object arg, String name, MethodParameter parameter,
			@Nullable ModelAndViewContainer mavContainer, NativeWebRequest request) {
		//解析参数 并放到pathVars内
		String key = View.PATH_VARIABLES;
		int scope = RequestAttributes.SCOPE_REQUEST;
		Map<String, Object> pathVars = (Map<String, Object>) request.getAttribute(key, scope);
		if (pathVars == null) {
			pathVars = new HashMap<>();
			request.setAttribute(key, pathVars, scope);
		}
		pathVars.put(name, arg);
	}
	@Override
	@SuppressWarnings("unchecked")
	@Nullable
	protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
	//在url中的属性已经被spring容器解析 并放入org.springframework.web.servlet.HandlerMapping.uriTemplateVariables的key里
	//如果我的url为a/{id},且我请求的时候给id赋值为1
	//那么这里获取到的 map形如{"id",1}
		Map<String, String> uriTemplateVars = (Map<String, String>) request.getAttribute(
				HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
				//如果map不为空 根据key(name)取出value值 
		return (uriTemplateVars != null ? uriTemplateVars.get(name) : null);
	}
}

经过发现,PathVariableMethodArgumentResolverRequestParamMethodArgumentResolver的继承体系是一样的,而且resolveArgument是写在父类的。下面来看下AbstractNamedValueMethodArgumentResolver是如何解析参数的

	//参数解析是一个参数一个参数的解析,
	//如我controller中@RequestParam String a,@RequestParam String b
	//则会执行两次参数解析
	@Override
	@Nullable
	public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
		//获取参数的NameValueInfo,这个NameValueInfo其实就是name/required/defaultValue,
		//相信使用过@RequestParam/@PathVariable注解的,应该比较熟悉,因为这是注解内的属性
		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()) {
			   //如果解析的参数值是空的,而且这个字段也是必须的,就会执行到这里。
			   //AbstractNamedValueMethodArgumentResolver这里是抛出异常处理
				handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
			}
			//如果某个参数不是必须的,并且解析出来的参数值是空
			//这里方法的内部实现是:如果arg不为空 则返回arg;
			//如果arg为空,如果方法类型为Boolean,则返回FALSE,
			//如果是其他的基本数据类型 则抛出异常
			arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
		}
		//如果解析出的arg为"",且默认值不为空
		else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
			arg = resolveStringValue(namedValueInfo.defaultValue);
		}

		if (binderFactory != null) {
			WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
			try {
				arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
			}
			catch (ConversionNotSupportedException ex) {
				throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
						namedValueInfo.name, parameter, ex.getCause());
			}
			catch (TypeMismatchException ex) {
				throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
						namedValueInfo.name, parameter, ex.getCause());

			}
		}
        //PathVariable注解时 会执行到这个方法
		handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);

		return arg;
	}

下面跟着RequestResponseBodyMethodProcessor类来看下Spring如何对@RequestBody@ResponseBody做处理的。
在这里插入图片描述
从类的继承关系可以看出,RequestResponseBodyMethodProcessor既实现了HandlerMethodArgumentResolver又实现了HandlerMethodReturnValueHandler,因此具有了解析入参和响应结果的能力。

先从解析入参开始,老套路了,既然实现了HandlerMethodArgumentResolver,那么肯定会先根据supportParameter来判断当前参数解析器是否能够解析该注解的参数,然后再决定是否解析。

RequestResponseBodyMethodProcessor类

   @Override
	public boolean supportsParameter(MethodParameter parameter) {
		//判断当前参数中是否有@RequestBody的注解,如果有则可以使用该参数解析器
		return parameter.hasParameterAnnotation(RequestBody.class);
	}
    @Override
	public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

		parameter = parameter.nestedIfOptional();
		//根据MediaType选择合适的HTTPMessageConverter 来读取请求信息
		//这个是解析参数的重要步骤,具体实现在其父类AbstractMessageConverterMethodArgumentResolver中做的操作
		Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
		String name = Conventions.getVariableNameForParameter(parameter);

		if (binderFactory != null) {
			WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
			if (arg != null) {
				validateIfApplicable(binder, parameter);
				if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
					throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
				}
			}
			if (mavContainer != null) {
				mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
			}
		}

		return adaptArgumentIfNecessary(arg, parameter);
	}

	@Override
	protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,
			Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

		HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
		Assert.state(servletRequest != null, "No HttpServletRequest");
		ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest);
		//调用父类AbstractMessageConverterMethodArgumentResolver中的readWithMessageConverters
		Object arg = readWithMessageConverters(inputMessage, parameter, paramType);
		if (arg == null && checkRequired(parameter)) {
			throw new HttpMessageNotReadableException("Required request body is missing: " +
					parameter.getExecutable().toGenericString(), inputMessage);
		}
		return arg;
	}

AbstractMessageConverterMethodArgumentResolver

public abstract class AbstractMessageConverterMethodArgumentResolver implements HandlerMethodArgumentResolver {


@Nullable
	protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
			Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

		MediaType contentType;
		boolean noContentType = false;
		try {
			//获取请求头的content-type
			contentType = inputMessage.getHeaders().getContentType();
		}
		catch (InvalidMediaTypeException ex) {
			throw new HttpMediaTypeNotSupportedException(ex.getMessage());
		}
		if (contentType == null) {
			noContentType = true;
			//如果contentType为空  为其设置默认值
			contentType = MediaType.APPLICATION_OCTET_STREAM;
		}
		//解析方法中的参数类型
		Class<?> contextClass = parameter.getContainingClass();
		Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
		if (targetClass == null) {
			ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
			targetClass = (Class<T>) resolvableType.resolve();
		}
		//获取Http请求方法	
		HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);
		Object body = NO_VALUE;

		EmptyBodyCheckingHttpInputMessage message;
		try {
			message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
			//messageConverters 是在SpringMVC上下文启动的加载进来的
			//有框架提供的默认值,有根据classpath来决定加载某messageConverter的
			for (HttpMessageConverter<?> converter : this.messageConverters) {
				Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
				GenericHttpMessageConverter<?> genericConverter =
						(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
						//根据具体messageConverter能处理的content-type与实际请求的content-type来判断能否被
						//messageConverter处理。
				if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
						(targetClass != null && converter.canRead(targetClass, contentType))) {
					if (message.hasBody()) {
						HttpInputMessage msgToUse =
								getAdvice().beforeBodyRead(message, parameter, targetType, converterType);			
						//如果能被具体某httpmessageconverter处理,则进行处理
						//比如 能被MappingJackson2HttpMessageConverter处理,能被其方法read处理
						//将请求参数 转为json		
						body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
								((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
						body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
					}
					else {
						body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
					}
					break;
				}
			}
		}
		catch (IOException ex) {
			throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage);
		}
		//如果当前上下文中没有能够处理当前content-type类型参数的HTTPMessageConverter存在 
		if (body == NO_VALUE) {
			//如果请求方法不存在或者不被支持或者请求参数没内容 那么返回null
			if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||
					(noContentType && !message.hasBody())) {
				return null;
			}
			//否则这里抛出异常 
			throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);
		}

		MediaType selectedContentType = contentType;
		Object theBody = body;
		LogFormatUtils.traceDebug(logger, traceOn -> {
			String formatted = LogFormatUtils.formatValue(theBody, !traceOn);
			return "Read \"" + selectedContentType + "\" to [" + formatted + "]";
		});

		return body;
	}
}

this.messageConverters如何被加载进上下文,可参考HttpMessageConverter

以上是常用注解的参数解析代码分析,但是由于SpringMVC框架 代码常用到很多设计模式,会出现 这一刻代码在这个类,下一刻就跑到了其某个实现类或父类等这样的情况。加之本人水平有限,所以会导致代码分析的时候,有种乱糟糟的感觉,望见谅。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值