Spring MVC 消息转换器

组件

HttpMessageConverter

处理HTTP请求和响应的转换器。

public interface HttpMessageConverter<T> {
	// 对于该Media类型是否可读
	boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
	boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
	
	// 获得当前消息转换器支持的类型
	List<MediaType> getSupportedMediaTypes();
	// 读写转换,将request或response中的参数转换为对应类型
	T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
			throws IOException, HttpMessageNotReadableException;

	void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException;
}

AbstractHttpMessageConverter

HttpMessageConverter接口的基础实现

public abstract class AbstractHttpMessageConverter<T> implements HttpMessageConverter<T> {
	// 消息转换器支持的Content-Type
	private List<MediaType> supportedMediaTypes = Collections.emptyList();	
	// 默认编码
	private Charset defaultCharset;	
	public boolean canRead(Class<?> clazz, @Nullable MediaType mediaType) {
		// 是否支持类型和content-type
		return supports(clazz) && canRead(mediaType);
	}
	// 开始read时转换
	public final T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
			throws IOException, HttpMessageNotReadableException {
 
		return readInternal(clazz, inputMessage);
	}
}

MediaType

添加了对HTTP规范中定义的content-type的支持。

public class MediaType extends MimeType implements Serializable {
	public static final String ALL_VALUE = "*/*";
	public static final String APPLICATION_JSON_VALUE = "application/json";
}	

RequestResponseBodyMethodProcessor

处理@RequestBody和@ResponseBody注解的参数转换和返回值转换器,根据MediaType来选择消息转换器处理。

// 支持方法POST PUT PATCH
public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
	// 持有的消息转换器
	protected final List<HttpMessageConverter<?>> messageConverters;
	// 支持的媒体类型
	protected final List<MediaType> allSupportedMediaTypes;
	// 事件通知
	private final RequestResponseBodyAdviceChain advice;
}	

源码分析

以RequestResponseBodyMethodProcessor为例,分析参数解析和返回值解析过程

参数解析

  1. 调用resolveArgument方法,通过消息转换器来解析request中的参数,返回解析后的参数值,如果有校验也在这进行。
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

	parameter = parameter.nestedIfOptional();
	// 通过消息转换器将request中数据转换为所需的java类型
	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());
			}
		}
		// 将校验结果放入model中
		if (mavContainer != null) {
			mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
		}
	}
	// 返回适配Optional后的参数值
	return adaptArgumentIfNecessary(arg, parameter);
}
  1. 将request请求装配为ServletServerHttpRequest,调用readWithMessageConverters方法解析参数。
protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,
			Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
	// 获得httpRequest请求
	HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
	// 将Request请求装配为ServletServerHttpRequest
	ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest);
	// 真正解析方法
	Object arg = readWithMessageConverters(inputMessage, parameter, paramType);
	// 如果参数为null且必要则抛异常
	if (arg == null && checkRequired(parameter)) {
		throw new HttpMessageNotReadableException("Required request body is missing: " +
				parameter.getExecutable().toGenericString(), inputMessage);
	}
	return arg;
}
  1. 将请求体内容转换为java类型,首先获得请求的content-type(默认使用application/octet-stream),遍历消息转换器找到支持当前content-type和参数类型的消息转换器来解析请求体
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
			Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

	MediaType contentType;
	boolean noContentType = false;
	try {
		// 获得request的content-type
		contentType = inputMessage.getHeaders().getContentType();
	}
	···无效类型异常
	
	// 如果请求未传送content-type,则使用application/octet-stream
	if (contentType == null) {
		noContentType = true;
		contentType = MediaType.APPLICATION_OCTET_STREAM;
	}
	// 获得当前参数所在的类
	Class<?> contextClass = parameter.getContainingClass();
	// 参数类型
	Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
	// 泛型的情况下参数类型为null,解析参数类型
	if (targetClass == null) {
		ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
		targetClass = (Class<T>) resolvableType.resolve();
	}
	// 获得请求方法POST|GET
	HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);
	// 默认请求体内容
	Object body = NO_VALUE;

	EmptyBodyCheckingHttpInputMessage message;
	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);
			// 消息转换器支持content-type和参数类型(优先使用支持泛型)
			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);
					// 请求体转换为对应的targetType类型
					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;
			}
		}
	}
	···抛出io异常
	// 如果请求体为空
	if (body == NO_VALUE) {
		// 如果method为null 或 不支持请求method 或 请求体为空且noContentType为true  返回null,否则抛出异常
		if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||
				(noContentType && !message.hasBody())) {
			return null;
		}
		throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);
	}
	
	···日志记录

	return body;
}

返回值解析

  1. 封装request和response,调用消息转换器处理返回值
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
	// 设置当前请求已被处理,不再进行视图渲染
	mavContainer.setRequestHandled(true);
	// 封装请求和响应
	ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
	ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

	// 调用消息转换器
	writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
  1. 处理返回值,判断返回值是否为Resource类型,通过accept和消息转换器支持的content-type选择合适的content-type作为响应头,通过消息转换器解析返回值(outputStream.flush响应客户端)
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
			ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

	Object body;
	Class<?> valueType;
	Type targetType;
	// 如果参数值为字符类型,设置返回值和返回值类型
	if (value instanceof CharSequence) {
		body = value.toString();
		valueType = String.class;
		targetType = String.class;
	}
	else {
		body = value;
		valueType = getReturnValueType(body, returnType);
		targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());
	}
	// 如果返回值为资源类型
	if (isResourceType(value, returnType)) {
		outputMessage.getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes");
		if (value != null && inputMessage.getHeaders().getFirst(HttpHeaders.RANGE) != null &&
				outputMessage.getServletResponse().getStatus() == 200) {
			Resource resource = (Resource) value;
			try {
				List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
				outputMessage.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value());
				body = HttpRange.toResourceRegions(httpRanges, resource);
				valueType = body.getClass();
				targetType = RESOURCE_REGION_LIST_TYPE;
			}
			catch (IllegalArgumentException ex) {
				outputMessage.getHeaders().set(HttpHeaders.CONTENT_RANGE, "bytes */" + resource.contentLength());
				outputMessage.getServletResponse().setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value());
			}
		}
	}
	// 获得返回值类型,content-type
	MediaType selectedMediaType = null;
	MediaType contentType = outputMessage.getHeaders().getContentType();
	boolean isContentTypePreset = contentType != null && contentType.isConcrete();
	if (isContentTypePreset) {
		// 如果response中设置的返回值类型
		selectedMediaType = contentType;
	}
	else {
		// 未设置返回值
		HttpServletRequest request = inputMessage.getServletRequest();
		// 获得请求头的accept
		List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);
		// 可返回类型
		List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
		// 如果返回值不为null且可提供content-type为空抛出异常
		if (body != null && producibleTypes.isEmpty()) {
			throw new HttpMessageNotWritableException(
					"No converter found for return value of type: " + valueType);
		}
		
		// 获得可用的content-type
		List<MediaType> mediaTypesToUse = new ArrayList<>();
		for (MediaType requestedType : acceptableTypes) {
			for (MediaType producibleType : producibleTypes) {
				if (requestedType.isCompatibleWith(producibleType)) {
					mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
				}
			}
		}
		// 可用类型为null处理
		if (mediaTypesToUse.isEmpty()) {
			if (body != null) {
				throw new HttpMediaTypeNotAcceptableException(producibleTypes);
			}
			if (logger.isDebugEnabled()) {
				logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);
			}
			return;
		}

		MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
		// 选择合适的content-type
		for (MediaType mediaType : mediaTypesToUse) {
			if (mediaType.isConcrete()) {
				selectedMediaType = mediaType;
				break;
			}
			else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {
				selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
				break;
			}
		}
	}

	if (selectedMediaType != null) {
		selectedMediaType = selectedMediaType.removeQualityValue();
		// 遍历消息转换器,选择合适的转换器解析返回值
		for (HttpMessageConverter<?> converter : this.messageConverters) {
			GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
					(GenericHttpMessageConverter<?>) converter : null);
			if (genericConverter != null ?
					((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
					converter.canWrite(valueType, selectedMediaType)) {
				body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
						(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
						inputMessage, outputMessage);
				if (body != null) {
					Object theBody = body;
					LogFormatUtils.traceDebug(logger, traceOn ->
							"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
					addContentDispositionHeader(inputMessage, outputMessage);
					if (genericConverter != null) {
						genericConverter.write(body, targetType, selectedMediaType, outputMessage);
					}
					else {
						((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
					}
				}
				else {
					···日志
				}
				return;
			}
		}
	}
	// 返回值不为空,且未被消息转换器处理
	if (body != null) {
		Set<MediaType> producibleMediaTypes =
				(Set<MediaType>) inputMessage.getServletRequest()
						.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);

		if (isContentTypePreset || !CollectionUtils.isEmpty(producibleMediaTypes)) {
			throw new HttpMessageNotWritableException(
					"No converter for [" + valueType + "] with preset Content-Type '" + contentType + "'");
		}
		throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值