SpringMVC的入参转换和响应参数转换

在上一篇文章中讲了大概的执行流程,这里详细讲一下入参转换和响应转换的实现原理
入参转换和响应转换的流程都是在通过Adapter调用HandlerMethod时发生的。
如下是请求头设置,SpringMVC会根据content-type和accept类型选择合适的HttpMessageConverter来进行消息的转换
在这里插入图片描述

参数转化

private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {

		MethodParameter[] parameters = getMethodParameters();
		Object[] args = new Object[parameters.length];
		for (int i = 0; i < parameters.length; i++) {
			MethodParameter parameter = parameters[i];
			parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
			args[i] = resolveProvidedArgument(parameter, providedArgs);
			if (args[i] != null) {
				continue;
			}
			//这里使用组合模式遍历所有参数解析器是否支持
			if (this.argumentResolvers.supportsParameter(parameter)) {
				try {
					args[i] = this.argumentResolvers.resolveArgument(
							parameter, mavContainer, request, this.dataBinderFactory);
					continue;
				}
			}
		}
		return args;
	}

在使用组合模式遍历所有参数解析器是否支持时,由于我们的的控制器参数使用@RequestBody修饰,所以在遍历到RequestResponseBodyMethodProcessor时,其supportsParameter会返回True

public boolean supportsParameter(MethodParameter parameter) {
		return parameter.hasParameterAnnotation(RequestBody.class);
	}

因此会使用RequestResponseBodyMethodProcessor来继续进行参数转化,其resolveArgument方法为

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

		parameter = parameter.nestedIfOptional();
		Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
		String name = Conventions.getVariableNameForParameter(parameter);

		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());
			}
		}
		mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());

		return adaptArgumentIfNecessary(arg, parameter);
	}

可以看到其是通过readWithMessageConverters来进行参数解析的

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

		HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
		ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest);
		Object arg = readWithMessageConverters(inputMessage, parameter, paramType);
		
		return arg;
	}

readWithMessageConverters()会对我们请求头中的content-type进行解析,并对目标参数对象进行实例化,之后对内置的所有HttpMessageConverter进行遍历,根据转换器的支持content-type类型来匹配处理
在这里插入图片描述
在这里插入图片描述
匹配的convert会对内容进行转换处理

响应转换

由于Rest应用存在@Response注解,在组合模式选择返回值类型转换器时,同样选择的是RequestResponseBodyMethodProcessor,其supportsReturnType方法内容如下,可以看到就是在判定是否存在@ResponseBody注解

public boolean supportsReturnType(MethodParameter returnType) {
		return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
				returnType.hasMethodAnnotation(ResponseBody.class));
	}

其他部分与入参类型转换基本相同,会解析请求支持的MediaType,通过ContentNegotiationManager来进行解析

public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
		for (ContentNegotiationStrategy strategy : this.strategies) {
			List<MediaType> mediaTypes = strategy.resolveMediaTypes(request);
			if (mediaTypes.isEmpty() || mediaTypes.equals(MEDIA_TYPE_ALL)) {
				continue;
			}
			return mediaTypes;
		}
		return Collections.emptyList();
	}

HeaderContentNegotiationStrategy会解析请求头中的Accept
在这里插入图片描述
拿到MediaType后,会再去拿controller方法所能产生的MediaType列表,并将accept的MediaType和这个MediaType列表进行兼容判断,将兼容的MediaType列表排序后选择最具体的(不包含通配符)作为最终选择的MediaType,然后寻找支持该MediaType的HttpMessageConverter进行消息转换

//这里拿到请求头中的Accept内容,如果没有则返回通配符 */* 代表所有类型列表
List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);
// 这里拿到controller方法produces描述的能够产生的媒体类型
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);
// 这里会对上面的俩进行兼容性判断,剔除掉不兼容的产生类型
Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
		for (MediaType requestedType : requestedMediaTypes) {
			for (MediaType producibleType : producibleMediaTypes) {
				if (requestedType.isCompatibleWith(producibleType)) {
					compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType));
				}
			}
		}
//兼容的产生类型做排序
List<MediaType> mediaTypes = new ArrayList<MediaType>(compatibleMediaTypes);
		MediaType.sortBySpecificityAndQuality(mediaTypes);
// 选择最具体的兼容性产生类型
		MediaType selectedMediaType = null;
		for (MediaType mediaType : mediaTypes) {
			if (mediaType.isConcrete()) {
				selectedMediaType = mediaType;
				break;
			}
			else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {
				selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
				break;
			}
		}

这里牵扯到了producesconsumes,如下代码

@RestController
public class TestController {

    @PostMapping(value = "/test",
                produces = "application/json;charset=utf-8",
                consumes = "application/json;charset=utf-8")
    public String test(@RequestBody MyRequest request){
        return "hello "+request.getName();

    }
}

该方法consumes指定了只能接受请求头中content-type为application/json;charset=utf-8的请求;produces表示该方法可以产生application/json;charset=utf-8的媒体类型;这俩都是数组可以写多个,这俩的字符编码保持一致否则会乱码

发布了98 篇原创文章 · 获赞 9 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/Mutou_ren/article/details/104080458
今日推荐