11、响应数据

news2024/12/24 2:34:38

文章目录

  • 1、响应JSON
    • 1.1、引入开发场景
    • 1.2 、jackson.jar + @ResponseBody
      • 1、装填返回值处理器
      • 2、返回值初步处理
      • 3、获取并使用返回值处理器
      • 4、观察如何获取返回值处理器
      • 5、返回值处理器接口内部
      • 6、返回值处理器支持的类型
      • 7、返回值解析器原理
    • 1.3、HTTPMessageConverter 原理
      • 1、MessageConverter接口
      • 2、系统中默认的 messageConverters
  • 2、内容协商
    • 2.1、引入xml依赖
    • 2.2、postman分别测试返回json和xml
    • 2.3、开启浏览器参数方式内容协商功能
    • 2.4、内容协商原理
    • 2.5、系统底层自动添加 converter
    • 2.6、判断Converters包的方式
    • 2.7、自定义 MessageConverter(消息转换器)
      • 1、需求
      • 2、实现逻辑
      • 3、自动配置类
      • 4、自定义内容协商策略


【尚硅谷】SpringBoot2零基础入门教程-讲师:雷丰阳
笔记

路还在继续,梦还在期许

1、响应JSON

1.1、引入开发场景

引入WEB场景

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

WEB场景自动引入了JSON场景

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-json</artifactId>
  <version>2.3.4.RELEASE</version>
  <scope>compile</scope>
</dependency>

JSON场景引入了JSON处理的相关依赖

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.11.2</version>
  <scope>compile</scope>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.datatype</groupId>
  <artifactId>jackson-datatype-jdk8</artifactId>
  <version>2.11.2</version>
  <scope>compile</scope>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.datatype</groupId>
  <artifactId>jackson-datatype-jsr310</artifactId>
  <version>2.11.2</version>
  <scope>compile</scope>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.module</groupId>
  <artifactId>jackson-module-parameter-names</artifactId>
  <version>2.11.2</version>
  <scope>compile</scope>
</dependency>

1.2 、jackson.jar + @ResponseBody

@Controller
public class ResponseTestController {

    @ResponseBody
    @GetMapping("/test/person")
    public Person getPerson() {
        Person person = new Person();
        person.setAge(28);
        person.setBirth(new Date());
        person.setUserName("zhangsan");
        return person;
    }
}

给前端自动返回JSON数据

1、装填返回值处理器

位置:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter

invokeHandlerMethod 方法

if (this.returnValueHandlers != null) {
	invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}

在这里插入图片描述

2、返回值初步处理

位置:org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod

invokeAndHandle 方法

/**
 * Invoke the method and handle the return value through one of the
 * configured {@link HandlerMethodReturnValueHandler HandlerMethodReturnValueHandlers}.
 * @param webRequest the current request
 * @param mavContainer the ModelAndViewContainer for this request
 * @param providedArgs "given" arguments matched by type (not resolved)
 */
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
		Object... providedArgs) throws Exception {

	// 请求执行后,返回一个返回值对象
	Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
	// 返回浏览器状态码
	setResponseStatus(webRequest);

	// 如果返回一个null对象,方法直接返回
	if (returnValue == null) {
		if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
			disableContentCachingIfNecessary(webRequest);
			mavContainer.setRequestHandled(true);
			return;
		}
	}
	// 如果返回一些失败原因
	else if (StringUtils.hasText(getResponseStatusReason())) {
		mavContainer.setRequestHandled(true);
		return;
	}

	// 返回值不为空,且不是一个字符串
	mavContainer.setRequestHandled(false);
	Assert.state(this.returnValueHandlers != null, "No return value handlers");
	try {
		// 处理返回值
		this.returnValueHandlers.handleReturnValue(
				// 获取返回值类型,并且传入返回值处理器
				returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
	}
	catch (Exception ex) {
		if (logger.isTraceEnabled()) {
			logger.trace(formatErrorForReturnValue(returnValue), ex);
		}
		throw ex;
	}
}

3、获取并使用返回值处理器

位置:org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite

handleReturnValue 方法

/**
 * Iterate over registered {@link HandlerMethodReturnValueHandler HandlerMethodReturnValueHandlers} and invoke the one that supports it.
 * @throws IllegalStateException if no suitable {@link HandlerMethodReturnValueHandler} is found.
 */
@Override // 处理返回值
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
		ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

	// 获取returnValue(返回值)与returnType(返回值类型),寻找哪个handler (返回值处理器)可以处理
	HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
	if (handler == null) {
		throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
	}
	// 使用返回值处理器处理
	handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}

4、观察如何获取返回值处理器

位置:org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite

selectHandler 方法

@Nullable
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
	// 是不是一个异步返回值(遍历循环所有返回值处理器),都不是会返回false
	boolean isAsyncValue = isAsyncReturnValue(value, returnType);
	// 遍历循环所有返回值处理器
	for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
		if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
			continue;
		}
		// 判断哪个返回值处理器可以处理
		if (handler.supportsReturnType(returnType)) {
			return handler;
		}
	}
	return null;
}

5、返回值处理器接口内部

位置:org.springframework.web.method.support.HandlerMethodReturnValueHandler

public interface HandlerMethodReturnValueHandler {

	/**
	 * Whether the given {@linkplain MethodParameter method return type} is
	 * supported by this handler.
	 * @param returnType the method return type to check
	 * @return {@code true} if this handler supports the supplied return type;
	 * {@code false} otherwise
	 */
	// 判断支持的返回值类型
	boolean supportsReturnType(MethodParameter returnType);

	/**
	 * Handle the given return value by adding attributes to the model and
	 * setting a view or setting the
	 * {@link ModelAndViewContainer#setRequestHandled} flag to {@code true}
	 * to indicate the response has been handled directly.
	 * @param returnValue the value returned from the handler method
	 * @param returnType the type of the return value. This type must have
	 * previously been passed to {@link #supportsReturnType} which must
	 * have returned {@code true}.
	 * @param mavContainer the ModelAndViewContainer for the current request
	 * @param webRequest the current request
	 * @throws Exception if the return value handling results in an error
	 */
	// 真正处理返回值的程序
	void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;

}

6、返回值处理器支持的类型

ModelAndView
Model
View
ResponseEntity
ResponseBodyEmitter
StreamingResponseBody
HttpEntity
HttpHeaders
Callable
DeferredResult
ListenableFuture
CompletionStage
WebAsyncTask
有 @ModelAttribute 且为对象类型的
@ResponseBody 注解 —> RequestResponseBodyMethodProcessor;

位置:org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor

supportsReturnType方法

	@Override
public boolean supportsReturnType(MethodParameter returnType) {
	// 使用工具类判断当前方法是否标注 ResponseBody 注解
	return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
			returnType.hasMethodAnnotation(ResponseBody.class));
}

7、返回值解析器原理

位置:org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor

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.
	// 使用消息转换器进行写出操作(利用MessageConverters将数据写为json)
	writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}

位置:org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor

writeWithMessageConverters 方法

/**
 * Writes the given return type to the given output message.
 * @param value the value to write to the output message
 * @param returnType the type of the value
 * @param inputMessage the input messages. Used to inspect the {@code Accept} header.
 * @param outputMessage the output message to write to
 * @throws IOException thrown in case of I/O errors
 * @throws HttpMediaTypeNotAcceptableException thrown when the conditions indicated
 * by the {@code Accept} header on the request cannot be met by the message converters
 * @throws HttpMessageNotWritableException thrown if a given message cannot
 * be written by a converter, or if the content-type chosen by the server
 * has no compatible converter.
 */
@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 媒体类型(也成为:内容协商)
	// 内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型)
	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 {
		// 获取原生的request对象
		HttpServletRequest request = inputMessage.getServletRequest();
		// 通过原生的request对象获取我们能接受的内容类型
		List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);
		// 服务器可以响应的内容类型
		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();
		// 遍历判断所有的 messageConverters(消息转换器),看哪个消息转换器可以将对象转换成json
		for (HttpMessageConverter<?> converter : this.messageConverters) {
			GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
					(GenericHttpMessageConverter<?>) converter : null);
			// canWrite 判断能不能支持写操作
			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(this.allSupportedMediaTypes);
	}
}
  • 1、返回值处理器判断是否支持这种类型返回值 supportsReturnType
  • 2、返回值处理器调用 handleReturnValue 进行处理
  • 3、RequestResponseBodyMethodProcessor 可以处理返回值标了@ResponseBody 注解的。
      1. 利用 MessageConverters 进行处理 将数据写为json
      • 1、内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型)
      • 2、服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据,
      • 3、SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter ,看谁能处理?
        • 1、得到MappingJackson2HttpMessageConverter可以将对象写为json
        • 2、利用MappingJackson2HttpMessageConverter将对象转为json再写出去。

1.3、HTTPMessageConverter 原理

1、MessageConverter接口

位置:org.springframework.http.converter.HttpMessageConverter

public interface HttpMessageConverter<T> {

	/**
	 * Indicates whether the given class can be read by this converter.
	 * @param clazz the class to test for readability
	 * @param mediaType the media type to read (can be {@code null} if not specified);
	 * typically the value of a {@code Content-Type} header.
	 * @return {@code true} if readable; {@code false} otherwise
	 */
	boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);

	/**
	 * Indicates whether the given class can be written by this converter.
	 * @param clazz the class to test for writability
	 * @param mediaType the media type to write (can be {@code null} if not specified);
	 * typically the value of an {@code Accept} header.
	 * @return {@code true} if writable; {@code false} otherwise
	 */
	boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);

	/**
	 * Return the list of {@link MediaType} objects supported by this converter.
	 * @return the list of supported media types, potentially an immutable copy
	 */
	List<MediaType> getSupportedMediaTypes();

	/**
	 * Read an object of the given type from the given input message, and returns it.
	 * @param clazz the type of object to return. This type must have previously been passed to the
	 * {@link #canRead canRead} method of this interface, which must have returned {@code true}.
	 * @param inputMessage the HTTP input message to read from
	 * @return the converted object
	 * @throws IOException in case of I/O errors
	 * @throws HttpMessageNotReadableException in case of conversion errors
	 */
	T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
			throws IOException, HttpMessageNotReadableException;

	/**
	 * Write an given object to the given output message.
	 * @param t the object to write to the output message. The type of this object must have previously been
	 * passed to the {@link #canWrite canWrite} method of this interface, which must have returned {@code true}.
	 * @param contentType the content type to use when writing. May be {@code null} to indicate that the
	 * default content type of the converter must be used. If not {@code null}, this media type must have
	 * previously been passed to the {@link #canWrite canWrite} method of this interface, which must have
	 * returned {@code true}.
	 * @param outputMessage the message to write to
	 * @throws IOException in case of I/O errors
	 * @throws HttpMessageNotWritableException in case of conversion errors
	 */
	void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException;

}

在这里插入图片描述

HttpMessageConverter: 看是否支持将 此 Class类型的对象,转为MediaType类型的数据。

例子:Person对象转为JSON。或者 JSON转为Person

2、系统中默认的 messageConverters

系统中默认的 messageConverters

系统中的messageConverters

0 - 只支持Byte类型的
1 - String
2 - String
3 - Resource
4 - ResourceRegion
5 - DOMSource.class \ SAXSource.class) \ StAXSource.class \StreamSource.class \Source.class
6 - MultiValueMap
7 - true
8 - true
9 - 支持注解方式xml处理的。

最终 MappingJackson2HttpMessageConverter 把对象转为JSON(利用底层的jackson的objectMapper转换的)

位置:org.springframework.http.converter.AbstractGenericHttpMessageConverter

write 方法

/**
 * This implementation sets the default headers by calling {@link #addDefaultHeaders},
 * and then calls {@link #writeInternal}.
 */
@Override
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();
	}
}

在这里插入图片描述

2、内容协商

通过遍历所有的MessageConverter,最终找到一个合适处理媒体类型数据的MessageConverter。

完整的内容协商功能:

根据客户端接收能力不同,返回不同媒体类型的数据。

安卓:返回XML
前端项目:返回JSON

2.1、引入xml依赖

<dependency>
	<groupId>com.fasterxml.jackson.dataformat</groupId>
	<artifactId>jackson-dataformat-xml</artifactId>
</dependency>

2.2、postman分别测试返回json和xml

只需要改变请求头中Accept字段。Http协议中规定的,告诉服务器本客户端可以接收的数据类型。

在这里插入图片描述

2.3、开启浏览器参数方式内容协商功能

为了方便内容协商,开启基于请求参数的内容协商功能。

spring:
  mvc:
    contentnegotiation:
      favor-parameter: true  #开启请求参数内容协商模式

发请求:
http://localhost:8080/test/person?format=json
http://localhost:8080/test/person?format=xml

在这里插入图片描述

确定客户端接收什么样的内容类型;
1、Parameter策略优先确定是要返回json数据(获取请求头中的format的值)

位置:org.springframework.web.accept.ParameterContentNegotiationStrategy

getMediaTypeKey 方法

@Override
@Nullable
// 获取请求头中的 format 的值
protected String getMediaTypeKey(NativeWebRequest request) {
	return request.getParameter(getParameterName());
}

2、最终进行内容协商返回给客户端json即可。

2.4、内容协商原理

  • 1、判断当前响应头中是否已经有确定的媒体类型。MediaType
  • 2、获取客户端(PostMan、浏览器)支持接收的内容类型。(获取客户端Accept请求头字段,值为:application/xml)
    • contentNegotiationManager(内容协商管理器)默认使用基于请求头的策略,获取客户端支持接受的内容类型
    • 在这里插入图片描述
    • HeaderContentNegotiationStrategy 确定客户端可以接收的内容类型
  • 3、遍历循环所有当前系统的 MessageConverter,看谁支持操作这个对象(Person)
  • 在这里插入图片描述
  • 4、找到支持操作Person的converter,把converter支持的媒体类型统计出来。
  • 在这里插入图片描述
  • 5、客户端需要【application/xml】。服务端能力有 10种,既能返回 json 又能 xml。
  • 6、进行内容协商的最佳匹配媒体类型
  • 在这里插入图片描述
  • 7、用 支持 将对象转为 最佳匹配媒体类型 的converter。调用它进行转化 。
  • 在这里插入图片描述

2.5、系统底层自动添加 converter

位置:org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport

addDefaultHttpMessageConverters 方法

/**
 * Adds a set of default HttpMessageConverter instances to the given list.
 * Subclasses can call this method from {@link #configureMessageConverters}.
 * @param messageConverters the list to add the default message converters to
 */
protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
	// 自动添加 converter
	messageConverters.add(new ByteArrayHttpMessageConverter());
	messageConverters.add(new StringHttpMessageConverter());
	messageConverters.add(new ResourceHttpMessageConverter());
	messageConverters.add(new ResourceRegionHttpMessageConverter());
	try {
		messageConverters.add(new SourceHttpMessageConverter<>());
	}
	catch (Throwable ex) {
		// Ignore when no TransformerFactory implementation is available...
	}
	messageConverters.add(new AllEncompassingFormHttpMessageConverter());

	if (romePresent) {
		messageConverters.add(new AtomFeedHttpMessageConverter());
		messageConverters.add(new RssChannelHttpMessageConverter());
	}

	// 导入了jackson处理xml的包,xml的converter就会自动进来
	if (jackson2XmlPresent) {
		Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.xml();
		if (this.applicationContext != null) {
			builder.applicationContext(this.applicationContext);
		}
		// 添加 jackson 处理 xml 的Converter
		messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build()));
	}
	else if (jaxb2Present) {
		messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
	}

	if (jackson2Present) {
		Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json();
		if (this.applicationContext != null) {
			builder.applicationContext(this.applicationContext);
		}
		messageConverters.add(new MappingJackson2HttpMessageConverter(builder.build()));
	}
	else if (gsonPresent) {
		messageConverters.add(new GsonHttpMessageConverter());
	}
	else if (jsonbPresent) {
		messageConverters.add(new JsonbHttpMessageConverter());
	}

	if (jackson2SmilePresent) {
		Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.smile();
		if (this.applicationContext != null) {
			builder.applicationContext(this.applicationContext);
		}
		messageConverters.add(new MappingJackson2SmileHttpMessageConverter(builder.build()));
	}
	if (jackson2CborPresent) {
		Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.cbor();
		if (this.applicationContext != null) {
			builder.applicationContext(this.applicationContext);
		}
		messageConverters.add(new MappingJackson2CborHttpMessageConverter(builder.build()));
	}
}

2.6、判断Converters包的方式

使用类工具,判断系统中是否有一下类。

解释了,当导入了jackson处理xml的包,xml的converter就会自动进来的原理。

位置:org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport

// 
static {
	ClassLoader classLoader = WebMvcConfigurationSupport.class.getClassLoader();
	romePresent = ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", classLoader);
	jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", classLoader);
	jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
			ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
	jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
	jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);
	jackson2CborPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", classLoader);
	gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
	jsonbPresent = ClassUtils.isPresent("javax.json.bind.Jsonb", classLoader);
}

2.7、自定义 MessageConverter(消息转换器)

1、需求

一个控制器方法,利用内容协商实现多协议数据兼容。

浏览器 发请求,返回 xml [application/xml] jacksonXmlConverter。
ajax 发请求,返回 json [application/json] jacksonXmlConverter。
app 发请求,返回自定义协议数据 [application/x-guigu] xxxxConverter。

2、实现逻辑

1、添加自定义的 MessageConverter 进系统底层。
2、系统底层就会统计出所有 MessageConverter 能操作哪些类型数据。
3、客户端内容协商需要 [application/x-guigu] 类型数据,自定义的 MessageConverter 就会起作用。

0、@ResponseBody 响应数据出去 调用 RequestResponseBodyMethodProcessor 处理
1、Processor 处理方法返回值。通过 MessageConverter 处理
2、所有 MessageConverter 合起来可以支持各种媒体类型数据的操作(读、写)
3、内容协商找到最终的 messageConverter;

3、自动配置类

SpringMVC 的自动配置类,WebMvcAutoConfiguration 中有一个类实现了 WebMvcConfigurer 配置类,内部配置了 MessageConverters。

在 spring boot 中,定制 springmvc 的功能,需要给容器中放入一个 WebMvcConfigurer 组件,在这个组件中,重写哪些方法,就是定制 springmvc 的哪些功能。

自动配置类

@Bean
public WebMvcConfigurer webMvcConfigurer(){
    return new WebMvcConfigurer() {

        // 扩展消息转换器
        @Override
        public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
            // 添加自定义Converters
            converters.add(new GuiguMessageconverter());
        }
    }
}

自定义MessageConverter

// 自定义Converter
public class GuiguMessageconverter implements HttpMessageConverter<Person> {
    @Override
    public boolean canRead(Class<?> clazz, MediaType mediaType) {
        return false;
    }

    @Override
    public boolean canWrite(Class<?> clazz, MediaType mediaType) {
        return clazz.isAssignableFrom(Person.class);
    }

    // 服务器要统计所有MessageConverter都能写出哪些内容类型
    @Override
    public List<MediaType> getSupportedMediaTypes() {
        return MediaType.parseMediaTypes("application/x-guigu");
    }

    @Override
    public Person read(Class<? extends Person> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        return null;
    }

    @Override
    public void write(Person person, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        // 自定义协议数据的写出
        String data = person.getUserName() + ";" + person.getAge() + ";" + person.getBirth();
        // 写出去
        OutputStream body = outputMessage.getBody();
        body.write(data.getBytes());
    }
}

4、自定义内容协商策略

在 WebMvcConfigurer 中自定义内容协商策略

// 自定义内容协商策略
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    // 传入参数策略支持的媒体类型
    Map<String, MediaType> mediaTypes = new HashMap<>();
    // 指定支持解析参数对应的媒体类型
    mediaTypes.put("json",MediaType.APPLICATION_JSON);
    mediaTypes.put("xml",MediaType.APPLICATION_XML);
    mediaTypes.put("gg",MediaType.parseMediaType("application/x-guigu"));
    // 参数的内容协商策略
    ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy(mediaTypes);
    // 将策略传入
    configurer.strategies(Arrays.asList(strategy));
}

内容协商管理器

在这里插入图片描述

有可能我们添加的自定义的功能会覆盖默认很多功能,导致一些默认的功能失效,在配置的时候,将默认的也添加上。

// 自定义内容协商策略
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    // 传入参数策略支持的媒体类型
    Map<String, MediaType> mediaTypes = new HashMap<>();
    // 指定支持解析参数对应的媒体类型
    mediaTypes.put("json",MediaType.APPLICATION_JSON);
    mediaTypes.put("xml",MediaType.APPLICATION_XML);
    mediaTypes.put("gg",MediaType.parseMediaType("application/x-guigu"));
    // 参数的内容协商策略
    ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy(mediaTypes);
    // 基于请求头的任容协商策略
    HeaderContentNegotiationStrategy headerContentNegotiationStrategy = new HeaderContentNegotiationStrategy();
    // 将策略传入
    configurer.strategies(Arrays.asList(strategy,headerContentNegotiationStrategy));
}

大家考虑,上述功能除了我们完全自定义外?SpringBoot有没有为我们提供基于配置文件的快速修改媒体类型功能?怎么配置呢?【提示:参照SpringBoot官方文档web开发内容协商章节】

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/430132.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

c# 通过webView2模拟登陆小红书网页版,解析无水印视频图片,以及解决X-s,X-t签名验证【2023年4月15日】

一、c# WebView2简介 1.一开始使用WebBrowser&#xff0c;因为WebBrowser控件使用的是ie内核&#xff0c;经过修改注册表切换为Edge内核后&#xff0c; 发现Edge内核版本较低&#xff0c;加载一些视频网站提示“浏览器版本过低“&#xff0c;”视频无法加载“。 2.WebBrowser…

CentOS上PHP源码安装和配置

CentOS上PHP源码安装和配置 此文是在CentOS 7上已经部署了Nginx的基础上进行的 关于CentOS7上安装Nginx&#xff0c;可参考我之前的文章&#xff1a; CentOS上Nginx安装记录 我们现在在这个基础上安装PHP 7。 PHP里面概念挺多的&#xff0c;没想到安装这个PHP需要花那么多时…

SpringBoot 表单提交全局日期格式转换器

参考资料 SpringBoot–LocalDateTime格式转换(前端入参)SpringBoot InitBinder注解绑定请求参数 目录 一. 实现Converter<S, T>接口的方式二. 全局ControllerAdvice InitBinder注解的方式三. RequestMappingHandlerAdapter的方式四. 效果 分析 ⏹当前台的提交数据的Con…

JVM-0418

JVM-字节码篇 虚拟机体系结构 线程共享&#xff1a;堆、方法区 线程私有&#xff1a;虚拟机栈&#xff0c;本地方法栈&#xff0c;程序计数器。其中虚拟机栈中包括局部变量表&#xff0c;和操作数栈。 字节码文件概述 字节码文件是跨平台的吗&#xff1f; 是的 Java虚拟机…

Apache Log4j2(CVE-2021-4101)远程代码执行漏洞复现

文章目录前言影响范围黑盒发现复现准备JNDILADPRMI漏洞复现Dnslog数据外带使用工具进行反弹shell防御与绕过防御绕过参考前言 Apache log4j是Apache的一个开源项目&#xff0c;Java的日志记录工具(同logback)。 log4j2中存在JNDI注入漏洞&#xff0c;当程序记录用户输入的数据…

Qt Quick - FileDialog文件对话框

FileDialog文件对话框使用总结一、概述二、使用三、常用属性四、常用例子1. 单选打开文本文件2. 单选保存文本文件一、概述 FileDialog提供了一个基本的文件选择器的功能&#xff1a;它允许用户选择现有的文件或目录&#xff0c;或者创建新的文件名。 对话框最初是不可见的。…

【性能测试学习】2023最有效的7大性能测试技术(建议收藏)

进入互联网时代&#xff0c;性能测试显得越来越重要&#xff0c;移动应用、web应用和物联网应用都需要进行性能测试和性能调优&#xff0c;而进行性能和负载测试会产生了大量的数据&#xff0c;这些数据难以分析。除了数据分析&#xff0c;我们还会遇到其它一些困难和挑战。 今…

数据结构和算法学习记录——认识二叉搜索树及二叉搜索树的查找操作(递归以及迭代实现-查找操作、查找最大和最小元素)

目录 二叉搜索树 二叉搜索树的一些操作函数 二叉搜索树的查找操作Find 递归实现 迭代实现 查找最大和最小元素 查找最小元素的递归函数 查找最大元素的迭代函数 二叉搜索树 二叉搜索树&#xff08;BST&#xff0c;Binary Search Tree&#xff09;&#xff0c;也称二…

深入了解 Hugging Face 中的生成工具:Generate方法

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️&#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

SSH升级

升级openssh版本一、安装telnet远程管理主机1、检查是否安装telnet2、安装telnet服务二、下载所需的安装包1、下载openssl、openssh、zlib安装包2、安装所需的相关软件3、备份原来的数据4、复制文件到/usr/local/bin/下增加执行权限一、安装telnet远程管理主机 1、检查是否安装…

通达信口袋支点选股公式编写和设置方法答疑

1、口袋支点选股公式成交量条件 在我编写的口袋支点选股公式中&#xff0c;成交量条件为成交量创10日新高。有网友提出&#xff0c;根据书中的定义&#xff0c;口袋支点成交量条件是成交量大于近10日下跌时的最大成交量。 这个问题确实是我没考虑周全&#xff0c;成交量创10日…

【5G NAS】NR 终端侧PDU建立过程以及数据包的过滤和映射

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G算力网络技术标准研究。 博客…

对数据去趋势

对数据去趋势 测量的信号可能显示数据中非固有的整体模式。这些趋势有时会妨碍数据分析&#xff0c;因此必须进行去趋势。 以具有不同趋势的两种心电图 (ECG) 信号为例。ECG 信号对电源干扰等扰动很敏感。加载信号并绘制它们。 load(ecgSignals.mat) t (1:length(ecgl));su…

LaTeX+Overleaf 论文速通教程

一、文本/排版二、章节和段落三、数学公式四、插入图片五、插入表格六、参考文献与交叉引用不使用BibTeX使用BibTeX(推荐)七、交叉引用label和refOverleaf开发界面 latex命令&#xff1a;\命令[可选参数]{必选参数} Latex项目组成&#xff1a; .tex&#xff1a;正文 .bib&…

如何对数据库进行优化

数据库是什么&#xff1f; 简单来说数据库就是将数据按照一定顺序存储到磁盘上的一个软件&#xff0c;我们平时写的sql语句&#xff0c;就是用数据库软件能识别的语言&#xff0c;对数据进行增删改查。其实数据本质上是不存在表里&#xff0c;而是存在磁盘上&#xff0c;所谓的…

掌握亚马逊,沃尔玛,东南亚平台的测评要点,测评事半功倍

测评其实最重要的两个点就是自己的资源和成号率 资源包括;商家资源&#xff0c;中介资源&#xff0c;礼品卡资源&#xff0c;还有买卖账号的渠道&#xff0c;ip资源 成号率这个直接影响的就是你个人投入成本的多&#xff0c;成号率越高&#xff0c;你的成本越低&#xff0c;但…

【Java版oj】day36Rational Arithmetic、Pre-Post

目录 一、Rational Arithmetic &#xff08;1&#xff09;原题再现 &#xff08;2&#xff09;问题分析 &#xff08;3&#xff09;完整代码 二、Pre-Post &#xff08;1&#xff09;原题再现 &#xff08;2&#xff09;问题分析 &#xff08;3&#xff09;完整代码 一、…

十七、小程序报错 真机调试预览失效 Error: Illegal Buffer

报错 电脑端微信开发者工具运行成功而真机调试预览失效 报错 MiniProgramError Illegal Buffer 报错 {errno: 600001, errMsg: “request:fail -200:net::ERR_CERT_COMMON_NAME_INVALID”} 前言&#xff1a;手头有个去年的微信小程序项目 年底甲方不在使用 所以停掉了服务器、…

互联网医院系统构建:探索开源云平台与互联网医院平台源码的融合

互联网医院系统作为一种新型医疗服务模式&#xff0c;将传统的医院门诊转化为在线咨询、远程会诊等形式&#xff0c;帮助患者更加方便地获得专业医疗服务。 在实现互联网医院系统的建设过程中&#xff0c;选择合适的云平台和医院平台源码是至关重要的。 首先&#xff0c;开源…

LVM逻辑卷管理

目录一、LVM简介1、逻辑卷管理磁盘的优点2、缺陷3、LVM概述图二、LVM的使用1、创建逻辑卷2、使用逻辑卷3、删除逻辑卷4、LVM扩容5、LVM缩容三、Snapshot&#xff08;快照功能&#xff09;1、LVM-snapshot简介2、利用snapshot做备份&#xff08;LV快照&#xff09;四、LVM数据迁…