Spring Mvc请求处理过程分析 --- 参数解析

Spring Mvc请求处理过程分析

调试示例

调试示例基于注解@RequestBody,请求的入参是json格式的请求,本文主要分析spring解析请求参数的过程。

调试过程

InvocableHandlerMethod的getMethodArgumentValues方法,会解析请求参数。

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];
			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) {
   
				// Leave stack trace for later, exception may actually be resolved and handled...
				if (logger.isDebugEnabled()) {
   
					String exMsg = ex.getMessage();
					if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
   
						logger.debug(formatArgumentError(parameter, exMsg));
					}
				}
				throw ex;
			}
		}
		return args;
	}

在上面的代码中:args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);完成对参数的解析。实际是HandlerMethodArgumentResolverComposite对象进行解析的:

public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
   

		HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
		if (resolver == null) {
   
			throw new IllegalArgumentException("Unsupported parameter type [" +
					parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
		}
		return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
	}

下面这个方法获取解析参数的解析器:

private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
   
		HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
		if (result == null) {
   
			for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
   
				if (resolver.supportsParameter(parameter)) {
   
					result = resolver;
					this.argumentResolverCache.put(parameter, result);
					break;
				}
			}
		}
		return result;
	}

参数MethodParameter包含了处理请求的我们定义的Controller的Method信息,也就是请求方法的完整路径和参数类型信息都在这。据此可以找到解析器。找到之后进入解析器的解析参数方法,也就是类HandlerMethodArgumentResolver的resolveArgument(parameter, mavContainer, webRequest, binderFactory);方法。

@Override
	public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
   

		parameter = parameter.nestedIfOptional();
		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);
	}

上述方法主要分以下几步:
首先要冲请求中读取参数值,这需要根据处理请求的controller的方法参数类型,来确定绑定的类型,方法如下:

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);

		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;
	}

里面的readWithMessageCoverters调用的是父类AbstractMessageConverterMethodArgumentResolver中的方法:

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

		MediaType contentType;
		boolean noContentType = false;
		try {
   
			contentType = inputMessage.getHeaders().getContentType();
		}
		catch (InvalidMediaTypeException ex) {
   
			throw new HttpMediaTypeNotSupportedException(ex.getMessage());
		}
		if (contentType == null) {
   
			noContentType = true;
			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();
		}

		HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);
		Object body = NO_VALUE;

		EmptyBodyCheckingHttpInputMessage message = null;
		try {
   
			message = new EmptyBodyCheckingHttpInputMessage(inputMessage);

			for (HttpMessageConverter<?> converter : this.messageConverters) {
   
				Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
				GenericHttpMessageConverter<?> genericConverter =
						(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
				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);
						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);
		}
		finally {
   
			if (message != null && message.hasBody()) {
   
				closeStreamIfNecessary(message.getBody());
			}
		}

		if (body == NO_VALUE) {
   
			if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||
					(noContentType && !message.hasBody())) {
   
				return null;
			}
			throw new HttpMediaTypeNotSupportedException(contentType,
					getSupportedMediaTypes(targetClass != null ? targetClass : Object.class));
		}

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

		return body;
	}

可以看到这里进行了真正的请求参数读取过程。上述方法首先读取请求参数的类型是什么比如是json还是字符串。然后将请求转化为EmptyBodyCheckingHttpInputMessage对象。在EmptyBodyCheckingHttpInputMessage的构造函数中会读取请求体中的数据。

public EmptyBodyCheckingHttpInputMessage(HttpInputMessage inputMessage) throws IOException {
   
			this.headers = inputMessage.getHeaders();
			InputStream inputStream = inputMessage.getBody();
			if (inputStream.markSupported()) {
   
				inputStream.mark(1);
				this.body = (inputStream.read() != -1 ? inputStream : null);
				inputStream.reset();
			}
			else {
   
				PushbackInputStream pushbackInputStream = new PushbackInputStream(inputStream);
				int b = pushbackInputStream.read();
				if (b == -1) {
   
					this.body = null;
				}
				else {
   
					this.body = pushbackInputStream;
					pushbackInputStream.unread(b);
				}
			}
		}

对于json类型请求参数,实际解析器是AbstractJackson2HttpMessageConverter的子类完成,在AbstractJackson2HttpMessageConverter的read方法中,如下:

@Override
	public Object read(Type type, @Nullable Class<?> contextClass, HttpInputMessage inputMessage)
			throws IOException, HttpMessageNotReadableException {
   

		JavaType javaType = getJavaType(type, contextClass);
		return readJavaType(javaType, inputMessage);
	}

readJavaType方法完成将请求体读取到java对象中的过程。上述javaType对象解析出来之后就是请求体绑定的java类型。
readJavaType方法如下:

private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) throws IOException {
   
		MediaType contentType = inputMessage.getHeaders().getContentType();
		Charset charset = getCharset(contentType);

		ObjectMapper objectMapper = selectObjectMapper(javaType.getRawClass(), contentType);
		Assert.state(objectMapper != null, "No ObjectMapper for " + javaType);

		boolean isUnicode = ENCODINGS.containsKey(charset.name()) ||
				"UTF-16".equals(charset.name()) ||
				"UTF-32".equals(charset.name());
		try {
   
			InputStream inputStream = StreamUtils.nonClosing(inputMessage.getBody());
			if (inputMessage instanceof MappingJacksonInputMessage) {
   
				Class<?> deserializationView = ((MappingJacksonInputMessage) inputMessage).getDeserializationView();
				if (deserializationView != null) {
   
					ObjectReader objectReader = objectMapper.readerWithView(deserializationView).forType(javaType);
					if (isUnicode) {
   
						return objectReader.readValue(inputStream);
					}
					else {
   
						Reader reader = new InputStreamReader(inputStream, charset);
						return objectReader.readValue(reader);
					}
				}
			}
			if (isUnicode) {
   
				return objectMapper.readValue(inputStream, javaType);
			}
			else {
   
				Reader reader = new InputStreamReader(inputStream, charset);
				return objectMapper.readValue(reader, javaType);
			}
		}
		catch (InvalidDefinitionException ex) {
   
			throw new HttpMessageConversionException("Type definition error: " + ex.getType(), ex);
		}
		catch (JsonProcessingException ex) {
   
			throw new HttpMessageNotReadableException("JSON parse error: " + ex.getOriginalMessage(), ex, inputMessage);
		}
	}

将数据转换成java对象,这里由于请求体是json,使用的是com.fasterxml.jackson.databind包下的ObjectMapper,读取数据到java对象。调用return objectMapper.readValue(inputStream, javaType);方法读取输入流到objectMapper,其中第二个参数是指定提取依据什么类型来提取数据。

到此就将请求数据读取成java对象了。

回到RequestResponseBodyMethodProcessor的resolveArgument方法。代码134行,获取参数名称,然后看是否有binderFactory,如果有会调用它的相关逻辑。主要是在validateIfApplicable完成的:

protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
   
		Annotation[] annotations = parameter.getParameterAnnotations();
		for (Annotation ann : annotations) {
   
			Object[] validationHints = ValidationAnnotationUtils.determineValidationHints(ann);
			if (validationHints != null) {
   
				binder.validate(validationHints);
				break;
			}
		}
	}

上述方法,会读取方法参数上是否有注解,有的话看下该注解是否要求对参数进行校验,即binder.validate(validationHints);。进入DataBinder的如下方法:

public void validate(Object... validationHints) {
   
		Object target = getTarget();
		Assert.state(target != null, "No target to validate");
		BindingResult bindingResult = getBindingResult();
		// Call each validator with the same binding result
		for (Validator validator : getValidators()) {
   
			if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator) {
   
				((SmartValidator) validator).validate(target, bindingResult, validationHints);
			}
			else if (validator != null) {
   
				validator.validate(target, bindingResult);
			}
		}
	}

校验还需要通过SpringValidatorAdapter来做适配:

@Override
	public void validate(Object target, Errors errors) {
   
		if (this.targetValidator != null) {
   
			processConstraintViolations(this.targetValidator.validate(target), errors);
		}
	}

其中targetValidator对象就是我们编写的校验器

相关推荐

  1. Spring Mvc请求处理过程分析 --- 参数

    2024-01-08 12:54:01       36 阅读
  2. SpringMVC启动与请求处理流程

    2024-01-08 12:54:01       21 阅读
  3. springboot请求参数

    2024-01-08 12:54:01       8 阅读
  4. 简单分析SpringMVC处理请求流程

    2024-01-08 12:54:01       20 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-01-08 12:54:01       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-01-08 12:54:01       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-01-08 12:54:01       19 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-01-08 12:54:01       20 阅读

热门阅读

  1. ubuntu apt 更换阿里云源

    2024-01-08 12:54:01       39 阅读
  2. 【python】生成器

    2024-01-08 12:54:01       33 阅读
  3. Docker Compose 安装

    2024-01-08 12:54:01       32 阅读
  4. LeetCode每日一题 | 383. 赎金信

    2024-01-08 12:54:01       37 阅读
  5. KMP算法学习

    2024-01-08 12:54:01       39 阅读
  6. mysql 高阶查询

    2024-01-08 12:54:01       34 阅读
  7. C语言-蓝桥杯2022年第十三届省赛真题-质因数个数

    2024-01-08 12:54:01       40 阅读