天天看點

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
    HandlerMethodArgumentResolver源碼分析
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
    HandlerMethodArgumentResolver源碼分析
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);
	}
}
           

經過發現,

PathVariableMethodArgumentResolver

RequestParamMethodArgumentResolver

的繼承體系是一樣的,而且

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

做處理的。

HandlerMethodArgumentResolver源碼分析

從類的繼承關系可以看出,

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架構 代碼常用到很多設計模式,會出現 這一刻代碼在這個類,下一刻就跑到了其某個實作類或父類等這樣的情況。加之本人水準有限,是以會導緻代碼分析的時候,有種亂糟糟的感覺,望見諒。