基于springboot2的springmvc执行流程(四)

基于springboot2的springmvc执行流程(四)

开始

上一节分析到springmvc把参数名称和参数值解析出来后通过反射执行controller得到返回值,接下来要做的就是根据拿到的返回值然后匹配对应的ReturnValueHandler来解析返回值,如下图:

在这里插入图片描述
今天就是要分析源码的点就是,如何匹配返回值处理器,返回值处理器是如何解析的,里面还有管内容协商的处理。

1、这里的属性returnValueHandlers是从哪里来的呢?
这个invokeAndHandle方法是在ServletInvocableHandlerMethod类中的
在之前的分析中我们知道这个类的returnValueHandlers属性来至于RequestMappingHandlerAdapter里面,如下图:
在这里插入图片描述
2、返回值处理器是根据什么原则来匹配的呢?

进入到handleReturnValue方法,如下图:在这里插入图片描述
这个方法是在返回值处理器组合对象里面的方法,所有的返回值处理都要先调用组合对象里面的方法作为统一入口,这就是组合设计模式,在spring里面很多地方都有这种设计模式。
显然,这个方法的HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);这行代码是选择处理器的逻辑,进入这个方法看看:
在这里插入图片描述
第一行代码是异步模式的才会起作用,我们这里不是异步模式先不管,这里的大致逻辑就是,循环遍历每个ReturnValueHandler,然后调用每个ReturnValueHandler的supportsReturnType方法来判断,这个返回值处理器是否支持当前返回值,所以核心匹配逻辑就是每个处理器的supportsReturnType方法。我这里的controller方法是用@ResponseBody修饰的,返回值处理器都是匹配到的RequestResponseBodyMethodProcesor。不同的返回值匹配的到的处理器可能不同,supportsReturnType方法的逻辑也不同,这里就以这个返回值处理器为例,进入supportsReturnType方法分析源码:
在这里插入图片描述
这两行代码就很明显了,就是看返回值类型所在类和所在方法是否有ResponseBody注解。

3、返回值处理器是如何处理返回值的呢?

在这里插入图片描述
我们这里就以RequestResponseBodyMethodProcesor为例分析,进入到RequestResponseBodyMethodProcesor类的handleReturnValue方法:

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

		// Try even with null return value. ResponseBodyAdvice could get involved.
		writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
	}

前三行代码没啥好解释的,第二三行代码就是把request和response包装了下。直接进入writeWithMessageConverters方法吧:

protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
			ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

		Object outputValue;
		Class<?> valueType;
		Type declaredType;

		if (value instanceof CharSequence) {
			outputValue = value.toString();
			valueType = String.class;
			declaredType = String.class;
		}
		else {
			outputValue = value;
			valueType = getReturnValueType(outputValue, returnType);
			declaredType = getGenericType(returnType);
		}

		if (isResourceType(value, returnType)) {
			outputMessage.getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes");
			if (value != null && inputMessage.getHeaders().getFirst(HttpHeaders.RANGE) != null) {
				Resource resource = (Resource) value;
				try {
					List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
					outputMessage.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value());
					outputValue = HttpRange.toResourceRegions(httpRanges, resource);
					valueType = outputValue.getClass();
					declaredType = 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());
				}
			}
		}


		List<MediaType> mediaTypesToUse;

		MediaType contentType = outputMessage.getHeaders().getContentType();
		if (contentType != null && contentType.isConcrete()) {
			mediaTypesToUse = Collections.singletonList(contentType);
		}
		else {
			HttpServletRequest request = inputMessage.getServletRequest();
			//就是从request的header里面取ACCEPT字段的值
			List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);
			//如果在controller中指定了produces就直接从request里面取出来,
			// 否则就根据返回值和返回值的类型匹配可以解析的converter然后把converter支持的媒体类型都返回
			List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);

			if (outputValue != null && producibleMediaTypes.isEmpty()) {
				throw new HttpMessageNotWritableException(
						"No converter found for return value of type: " + valueType);
			}
			mediaTypesToUse = new ArrayList<>();
			for (MediaType requestedType : requestedMediaTypes) {
				for (MediaType producibleType : producibleMediaTypes) {
					if (requestedType.isCompatibleWith(producibleType)) {
						mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
					}
				}
			}
			if (mediaTypesToUse.isEmpty()) {
				if (outputValue != null) {
					throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
				}
				return;
			}
			MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
		}

		MediaType selectedMediaType = null;
		for (MediaType mediaType : mediaTypesToUse) {
			if (mediaType.isConcrete()) {
				selectedMediaType = mediaType;
				break;
			}
			else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {
				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(declaredType, valueType, selectedMediaType) :
						converter.canWrite(valueType, selectedMediaType)) {
					outputValue = getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
							(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
							inputMessage, outputMessage);
					if (outputValue != null) {
						addContentDispositionHeader(inputMessage, outputMessage);
						if (genericConverter != null) {
							genericConverter.write(outputValue, declaredType, selectedMediaType, outputMessage);
						}
						else {
							((HttpMessageConverter) converter).write(outputValue, selectedMediaType, outputMessage);
						}
						if (logger.isDebugEnabled()) {
							logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +
									"\" using [" + converter + "]");
						}
					}
					return;
				}
			}
		}

		if (outputValue != null) {
			throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
		}
	}

这个方法实在是太长了,都超出了idea的方法长度限制了,看来springmvc的相关作者有时候也不是很规范。

前面代码没有太多核心逻辑,我们直接进入内容协商的核心逻辑吧:

MediaType contentType = outputMessage.getHeaders().getContentType();
		if (contentType != null && contentType.isConcrete()) {
			mediaTypesToUse = Collections.singletonList(contentType);
		}
		else {
			HttpServletRequest request = inputMessage.getServletRequest();
			//就是从request的header里面取ACCEPT字段的值
			List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);
			//如果在controller中指定了produces就直接从request里面取出来,
			// 否则就根据返回值和返回值的类型匹配可以解析的converter然后把converter支持的媒体类型都返回
			List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);

			if (outputValue != null && producibleMediaTypes.isEmpty()) {
				throw new HttpMessageNotWritableException(
						"No converter found for return value of type: " + valueType);
			}
			mediaTypesToUse = new ArrayList<>();
			for (MediaType requestedType : requestedMediaTypes) {
				for (MediaType producibleType : producibleMediaTypes) {
					if (requestedType.isCompatibleWith(producibleType)) {
						mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
					}
				}
			}
			if (mediaTypesToUse.isEmpty()) {
				if (outputValue != null) {
					throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
				}
				return;
			}
			MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
		}

1、首先会从response里面取出content-type,如果我们在代码里面设置了content-type的内容,代码直接进入第一个分支,那么用于返回的媒体类型就被强制规定为content-type所指定的媒体类型。
2、一般我们都不会在代码里面设置content-Type,所以代码会进入else分支。
3、这个分支里面前面的代码我都写了注释,现在解释一下:

mediaTypesToUse = new ArrayList<>();
			for (MediaType requestedType : requestedMediaTypes) {
				for (MediaType producibleType : producibleMediaTypes) {
					if (requestedType.isCompatibleWith(producibleType)) {
						mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
					}
				}
			}

这段代码。这里其实就是根据浏览器ACCEPT字段先取到浏览器能支持的媒体类型,然后根据produces配置的输出媒体类型,或者是converter支持的媒体类型取到能输出的媒体类型,它们取交集,就是本次可以输出的媒体类型,把可以输出的媒体类型放到mediaTypesToUse集合里面,后面要用到这些媒体类型。

4、既然得到了可以使用的媒体类型,那么总要根据一定规则得到一个最合适的媒体类型吧?

	MediaType selectedMediaType = null;
		for (MediaType mediaType : mediaTypesToUse) {
			if (mediaType.isConcrete()) {
				selectedMediaType = mediaType;
				break;
			}
			else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {
				selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
				break;
			}
		}

循环可用媒体类型计算得到,第一个if分支,如果是确定的媒体类型(就是没有带*的)就直接赋值给selectedMediaType变量,就是本次内容协商选择的媒体类型。
5、得到了最终的媒体类型,那么就可以处理返回值了

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(declaredType, valueType, selectedMediaType) :
						converter.canWrite(valueType, selectedMediaType)) {
					outputValue = getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
							(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
							inputMessage, outputMessage);
					if (outputValue != null) {
						addContentDispositionHeader(inputMessage, outputMessage);
						if (genericConverter != null) {
							genericConverter.write(outputValue, declaredType, selectedMediaType, outputMessage);
						}
						else {
							((HttpMessageConverter) converter).write(outputValue, selectedMediaType, outputMessage);
						}
						if (logger.isDebugEnabled()) {
							logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +
									"\" using [" + converter + "]");
						}
					}
					return;
				}
			}
		}

现在的问题就是用哪个转换器,怎么转化的问题了。

1、用哪个转行器呢?
首先循环所有的转化器,匹配它是否能写这个变量。我这里返回值是一个string类型,所以最终匹配到的就是一个StringHttpMessageConverter转换器。
进入它的canWrite方法看看它是如何判断是否匹配这个值的呢?

在这里插入图片描述

其实就两个逻辑:
(1)是不是支持这个返回值类型
(2)是不是支持这个返回值的媒体类型。

接下来匹配到了返回值转换器,第一步先执行controller的切面逻辑(这个不是这里的重点,就先不解析了),然后就是真正的转化返回值了。
2、如何转化的呢?
进入到StringHttpMessageConverter的write方法:

	public final void write(final T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException {

		final HttpHeaders headers = outputMessage.getHeaders();
		addDefaultHeaders(headers, t, contentType);

		if (outputMessage instanceof StreamingHttpOutputMessage) {
			StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;
			streamingOutputMessage.setBody(outputStream -> writeInternal(t, new HttpOutputMessage() {
				@Override
				public OutputStream getBody() {
					return outputStream;
				}
				@Override
				public HttpHeaders getHeaders() {
					return headers;
				}
			}));
		}
		else {
			writeInternal(t, outputMessage);
			outputMessage.getBody().flush();
		}
	}

这里显然我们不是流式处理所以走得是else逻辑,我们再进入到writeInternal方法:

	@Override
	protected void writeInternal(String str, HttpOutputMessage outputMessage) throws IOException {
		if (this.writeAcceptCharset) {
			outputMessage.getHeaders().setAcceptCharset(getAcceptedCharsets());
		}
		Charset charset = getContentTypeCharset(outputMessage.getHeaders().getContentType());
		StreamUtils.copy(str, charset, outputMessage.getBody());
	}

这里就很显而易见了,就是把返回值的byte数组直接写入到respose的body里面。整个处理流程核心逻辑就完成了。后面还有一些后续处理,但是基本上都是没有什么逻辑要执行的,就是判断一下response是否已经被处理了,是否还要处理,是否还要渲染页面,我们这个流程,明显后面的都不需要处理了。

总结

总结起来返回值处理就以下步骤:

1、匹配返回值处理器
2、内容协商,确定最终媒体类型
3、匹配返回值转行器
4、通过转换器把返回值转化到response里面。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值