首页 > 其他分享 >SpringMVC-解析@ResponseBody

SpringMVC-解析@ResponseBody

时间:2022-11-12 22:15:02浏览次数:58  
标签:body null contentType SpringMVC outputMessage value ResponseBody returnType 解析

ServletInvocableHandlerMethod.invokeAndHandle处理完request得到结果后调用returnValueHandlers.handleReturnValue处理返回值。

HandlerMethodReturnValueHandlerComposite.handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)

	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
		ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

	HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
	if (handler == null) {
		throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
	}
	handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}


private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
	boolean isAsyncValue = isAsyncReturnValue(value, returnType);
	for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
		if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
			continue;
		}
		if (handler.supportsReturnType(returnType)) {
			return handler;
		}
	}
	return null;
}

遍历returnValueHandlers,调用HandlerMethodReturnValueHandler.supportsReturnType判断返回值解析器是否支持解析返回值,支持则返回。调用返回值解析器的handleReturnValue解析返回值。@ResponseBody由RequestResponseBodyMethodProcessor处理。

RequestResponseBodyMethodProcessor.supportsReturnType(MethodParameter returnType)

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

判断类或方法上是否有@ResponseBody注解。

RequestResponseBodyMethodProcessor.handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)

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

创建ServletServerHttpRequest和ServletServerHttpResponse。调用writeWithMessageConverters处理返回值。

AbstractMessageConverterMethodProcessor.writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)

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 (body != null) {
				throw new HttpMediaTypeNotAcceptableException(producibleTypes);
			}
			if (logger.isDebugEnabled()) {
				logger.debug("No match for " + acceptableTypes + ", supported: " + 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()));
	}
}

1、解析返回值类型
2、如果返回值类型是Resource,则进行处理
3、从ServletServerHttpResponse获取contentType,即MediaType,如果不存在则根据返回值类型从HttpServletRequest中选择出contentType。
4、遍历messageConverters,调用消息转换器的canWrite判断消息转换器是否可写入返回值,如果支持则调用消息转换器的write向ServletServerHttpResponse写入消息。如果messageConverter是GenericHttpMessageConverter类型,canWrite是通过MediaType来判断消息转换器是否可写入返回值。其他消息转换器是通过返回值类型和MediaType来判断否可写入返回值。
@ResponseBody标注的方法返回值由MappingJackson2HttpMessageConverter解析。

MappingJackson2HttpMessageConverter的父类AbstractGenericHttpMessageConverter.write(final T t, @Nullable final Type type, @Nullable MediaType contentType,
HttpOutputMessage outputMessage)

public final void write(final T t, @Nullable final Type type, @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, type, new HttpOutputMessage() {
			@Override
			public OutputStream getBody() {
				return outputStream;
			}
			@Override
			public HttpHeaders getHeaders() {
				return headers;
			}
		}));
	}
	else {
		writeInternal(t, type, outputMessage);
		outputMessage.getBody().flush();
	}
}

1、addDefaultHeaders向HttpOutputMessage的header写入ContentType和ContentLength
2、如果HttpOutputMessage是StreamingHttpOutputMessage类型调用StreamingHttpOutputMessage.setBody。否则调用writeInternal向HttpOutputMessage写入消息。刷新写入的消息到HttpOutputMessage。

AbstractJackson2HttpMessageConverter.writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage)

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

调用objectWriter.writeValue写入消息。

标签:body,null,contentType,SpringMVC,outputMessage,value,ResponseBody,returnType,解析
From: https://www.cnblogs.com/shigongp/p/16884741.html

相关文章

  • spring底层核心概念解析
    1.BeanDefinition包含bean的一些基本元信息,如bean的类型,作用域,初始化方法...等等。申明式的定义,如@Bean,等等<beanclass="com.test.service.UserService"id="userSe......
  • SpringMVC-解析@PathVariable
    DispatcherServlet.doService去处理请求时,调用getHandler去获取匹配请求的handle。会调用RequestMappingInfoHandlerMapping.handleMatch,这个方法会调用extractMatchDetai......
  • 这些不知道,别说你熟悉 Nacos,深度源码解析!
    SpringCloud应用启动拉去配置我们之前写过一篇文章,介绍了一些Spring提供的扩展机制。其中说到了ApplicationContextInitializer,该扩展是在上下文准备阶段(prepareContext......
  • Go 语言项目源码解析:定时任务库 cron
    环境准备首先我们将源码克隆(Fork)为自己的个人仓库,只需要在GitHub项目主页点击Fork按钮,然后输入项目名称点击确认即可。克隆完毕后,可以下载到本地,或者直接在科隆后的Git......
  • SpringMVC-解析对象
    如果参数是对象且没有注解,则参数由ServletModelAttributeMethodProcessor解析。解析如下参数:@PostMapping("/userParam0")@ResponseBodypublicUserhandleUserByParam0......
  • python 某猫投诉加密解析
    importhashlibimportjsonimportrandomimporttimeimportrequestssha256=hashlib.sha256()'''某猫投诉https://jiangsu.tousu.sina.com.cn/'''classBlackCatComplain......
  • SpringMVC
    SpringMVC:学习视频-狂神说SpringMVC:视频链接SpringMVCssm:mybatis+Spring+SpringMVCMVC三层架构JavaSE:认真学习,老师带,入门快!JavaWeb:认真学习,老师带,入门快!......
  • SpringMVC-解析@RequestParam参数
    InvocableHandlerMethod.invokeForRequest执行请求时会调用InvocableHandlerMethod.getMethodArgumentValues解析方法参数。InvocableHandlerMethod.getMethodArgumentVal......
  • Linux开启DNS Server后解析外域受限问题
      CentOS7系统里起了一个DNSServer,一切顺利,针对本地域的各项解析都很成功,欢欣鼓舞。 在欢欣鼓舞时却发觉DNS服务器无法进行域外域名解析,查找原因发现除了本地定义的......
  • 快速掌握kafka原理解析
    ​简介​Kafka是最初由Linkedin公司开发,是一个分布式、支持分区的(partition)、多副本的(replica),基于zookeeper协调的分布式消息系统,它的最大的特性就是可以实时的处理大量数......