【springboot】HTTPMessageConvertor源码分析原理

承接上一篇文章,还是按照之前的顺序来debug,先进入invokeAndHandle执行方法,可以看到this.returnValueHandlers是所有可以匹配的返回值处理器,通过supportsReturnType方法判断是否支持该类型的返回值后匹配到了目标的ReturnValueHandler,就可以执行它的handleReturnValue方法,也就是上篇文章中文末提到的处理返回值的方法handleReturnValue(),进入最内层的handleReturnValue方法。

上文也已经分析匹配到了RequestResponseBodyMethodProcessor是处理这个例子的handler(也叫processor,处理器),这个处理器就是处理标注了@ResponseBody注解的方法的,可以处理它的返回值。

进入处理的writeWithMessageConverters方法,传进返回值的值、返回值的类型、输出数据和输出数据。顾名思义,就是利用messageConverters(信息类型转换器)将数据以json的类型写入。

首先我们put的数据不是字符类型的(charSequence),走else部分逻辑。

 valueType是我们传入值的类型,targetType是我们目标写入的数据类型。

还可以判断返回值是否为Resource(资源)类型的,如果是就会调用处理流数据(stream)的逻辑。

下面一段看到MediaType,MediaType就涉及到我们的内容协商原理。 

        内容协商就是浏览器告诉客户端自己能接收什么类型的数据,即浏览器页面的请求头信息中以Accept-(接收)为首的数据:

对于不同的接收类型数据有不同的权重(q)。

可以看到在writeWithMessageConverters方法中具体的处理逻辑也是先获得可以接收的类型(acceptableTypes),而acceptableTypes正好就是浏览器请求头中(Accept-)的信息。 

①acceptableTypes是该请求可以接受的类型,一共有七种。(入)

我们不难发现,数组中的七个值正好就是浏览器请求头中(Accept-)的信息。 

②producibleTypes是可以生成的类型。(出)

找到了①入(acceptableTypes)和②出(producibleTypes)后,双层for循环匹配①②。

如果还是没有匹配的类型(mediaTypeToUse为空),就会报错或返回debug。

如果成功匹配到,就会生成mediaTypes数组如下:

如无意外,完成处理后会得到最终的一个selectedMediaType(选定了的)。

此时可以看到seletedMediaType的值

然后才进到我们这篇文章的核心——HttpMessageConverter处理返回值。

查看this.messageConverters有哪些值,有哪些converter。

同理,SpringMVC会遍历底层的所有HttpMessageConverter接口,找到合适的converter。

查看HttpMessageConverter总接口:

查看HttpMessageConverter总接口的方法,其中canRead和canWrite方法判断是否支持(Class类型入、MediaType类型出)的读、写操作。在我们这里的例子就是Person类型的对象转为JSON类型,当然也支持JSON转Person类型。视具体要求而定。也说明了转换的过程是可逆的,如果要写出就是Person类型的对象转为JSON,写入则相反。

下面就不同的messageConverter按顺序说明(可参照上几张图):

                                           其中,第7个converter需要注意!!

(7和8都是处理同两种数据类型相互转换的,不过是处理的Encoding编码方式不一样,7是处理UTF-8的,8是处理ISO-8859的——>>后文writeInternal方法中会看到编码格式的处理)

可以查看MappingJacksonHttpMessageConveter接口中:

supports返回值永远为true可以说明, MappingJacksonHttpMessageConveter支持任何类型。(就是兜底的,万金油converter)而且支持的MediaType是以下两种:

注意:这里有一个三元判断符,都是进行能否写入的判断的(canWrite方法):

进入canWrite方法:

(最内层的canWrite方法还是前文看过的。)

最后判断都完成后,调用write方法写入:

然后进入写入的核心方法write():

可以看到headers的值就是Content-Type的值(MappingJacksonHttpMessageConveter指定)

write方法中的处理核心还是writeInternal方法:

最后来到 ,之所以能转成JSON类型,就是通过objectWriter写入数据。

(可用复习一下IO流,要实现写入写出数据,要记得flush一下才能成功。

同时还可以看到outputMessage的内容为: 

outputBuffer中存放输出流的数据,可以看到是JSON类型的。即已经将我们的Class类型的数据转为JSON类型的数据输出了。

(可以自己探索下不同类型的converter匹配的是什么类型的数据、注解等,写一些小案例实现)

【补充】下面是writeInternal方法的源码:

@Override
	protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException {

		MediaType contentType = outputMessage.getHeaders().getContentType();
		JsonEncoding encoding = getJsonEncoding(contentType);

		Class<?> clazz = (object instanceof MappingJacksonValue ?
				((MappingJacksonValue) object).getValue().getClass() : object.getClass());
		ObjectMapper objectMapper = selectObjectMapper(clazz, contentType);
		Assert.state(objectMapper != null, "No ObjectMapper for " + clazz.getName());

		OutputStream outputStream = StreamUtils.nonClosing(outputMessage.getBody());
		try (JsonGenerator generator = objectMapper.getFactory().createGenerator(outputStream, encoding)) {
			writePrefix(generator, object);

			Object value = object;
			Class<?> serializationView = null;
			FilterProvider filters = null;
			JavaType javaType = null;

			if (object instanceof MappingJacksonValue) {
				MappingJacksonValue container = (MappingJacksonValue) object;
				value = container.getValue();
				serializationView = container.getSerializationView();
				filters = container.getFilters();
			}
			if (type != null && TypeUtils.isAssignable(type, value.getClass())) {
				javaType = getJavaType(type, null);
			}

			ObjectWriter objectWriter = (serializationView != null ?
					objectMapper.writerWithView(serializationView) : objectMapper.writer());
			if (filters != null) {
				objectWriter = objectWriter.with(filters);
			}
			if (javaType != null && javaType.isContainerType()) {
				objectWriter = objectWriter.forType(javaType);
			}
			SerializationConfig config = objectWriter.getConfig();
			if (contentType != null && contentType.isCompatibleWith(MediaType.TEXT_EVENT_STREAM) &&
					config.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
				objectWriter = objectWriter.with(this.ssePrettyPrinter);
			}
			objectWriter.writeValue(generator, value);

			writeSuffix(generator, object);
			generator.flush();
		}
		catch (InvalidDefinitionException ex) {
			throw new HttpMessageConversionException("Type definition error: " + ex.getType(), ex);
		}
		catch (JsonProcessingException ex) {
			throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getOriginalMessage(), ex);
		}
}

最后附上writeWithMessageConverters方法的源码如下:

@SuppressWarnings({"rawtypes", "unchecked"})
	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());
				}
			}
		}

		MediaType selectedMediaType = null;
		MediaType contentType = outputMessage.getHeaders().getContentType();
		boolean isContentTypePreset = contentType != null && contentType.isConcrete();
		if (isContentTypePreset) {
			if (logger.isDebugEnabled()) {
				logger.debug("Found 'Content-Type:" + contentType + "' in response");
			}
			selectedMediaType = contentType;
		}
		else {
			HttpServletRequest request = inputMessage.getServletRequest();
			List<MediaType> acceptableTypes;
			try {
				acceptableTypes = getAcceptableMediaTypes(request);
			}
			catch (HttpMediaTypeNotAcceptableException ex) {
				int series = outputMessage.getServletResponse().getStatus() / 100;
				if (body == null || series == 4 || series == 5) {
					if (logger.isDebugEnabled()) {
						logger.debug("Ignoring error response content (if any). " + ex);
					}
					return;
				}
				throw ex;
			}
			List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);

			if (body != null && producibleTypes.isEmpty()) {
				throw new HttpMessageNotWritableException(
						"No converter found for return value of type: " + valueType);
			}
			List<MediaType> mediaTypesToUse = new ArrayList<>();
			for (MediaType requestedType : acceptableTypes) {
				for (MediaType producibleType : producibleTypes) {
					if (requestedType.isCompatibleWith(producibleType)) {
						mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
					}
				}
			}
			if (mediaTypesToUse.isEmpty()) {
				if (logger.isDebugEnabled()) {
					logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);
				}
				if (body != null) {
					throw new HttpMediaTypeNotAcceptableException(producibleTypes);
				}
				return;
			}

			MediaType.sortBySpecificityAndQuality(mediaTypesToUse);

			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 (logger.isDebugEnabled()) {
				logger.debug("Using '" + selectedMediaType + "', given " +
						acceptableTypes + " and supported " + producibleTypes);
			}
		}

		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 {
						if (logger.isDebugEnabled()) {
							logger.debug("Nothing to write: null body");
						}
					}
					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(getSupportedMediaTypes(body.getClass()));
		}
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值