目录

一、springmvc响应json

1. web场景自动引入了json场景

2.使用方式

二、springmvc响应json数据原理

1.springmvc请求处理逻辑

2.返回值的处理

3.返回值处理器

4.SpringMVC到底支持哪些返回值

5.处理@ResponseBody 注解的RequestResponseBodyMethodProcessor

6.内容协商

7.HttpMessageConverter

8.MappingJackson2HttpMessageConverter

9.总结

三、初识内容协商

1.什么是内容协商

2.springboot响应xml报文

四、内容协商的原理

1.关键代码AbstractMessageConverterMethodProcessor

2.各种服务器端支持的Converts

3.获取客户端(PostMan、浏览器)支持接收的内容类型

4.遍历服务器所有MessageConverter,看谁支持操作这个对象(Person)

5.服务端支持的10种处理数据类型(MediaType)

6.权重优先匹配原则

7.开启基于请求参数的内容协商功能

五、自定义MessageConverter

1.功能

2.实现

3.测试

4.使用参数方式


一、springmvc响应json

1. web场景自动引入了json场景

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

引入web场景会自动引入json,json默认是使用jackson解析的。

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

2.使用方式

(1)@ResponseBody

在方法上加@ResponseBody

(2)@RestController

实际上@RestController=@Controller+@ResponseBody

将实体类转成json格式,给前端自动返回json数据。

二、springmvc响应json数据原理

1.springmvc请求处理逻辑

springmvc处理逻辑参考博文:

springBoot-springMVC请求处理原理_A_art_xiang的博客-CSDN博客

2.返回值的处理

@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ServletWebRequest webRequest = new ServletWebRequest(request, response);try {WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);if (this.argumentResolvers != null) { // 设置请求参数解析器invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);}if (this.returnValueHandlers != null) { // 设置响应值处理器invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);}invocableMethod.setDataBinderFactory(binderFactory);invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);ModelAndViewContainer mavContainer = new ModelAndViewContainer();mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));modelFactory.initModel(webRequest, mavContainer, invocableMethod);mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);asyncWebRequest.setTimeout(this.asyncRequestTimeout);WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);asyncManager.setTaskExecutor(this.taskExecutor);asyncManager.setAsyncWebRequest(asyncWebRequest);asyncManager.registerCallableInterceptors(this.callableInterceptors);asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);if (asyncManager.hasConcurrentResult()) {Object result = asyncManager.getConcurrentResult();mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];asyncManager.clearConcurrentResult();LogFormatUtils.traceDebug(logger, traceOn -> {String formatted = LogFormatUtils.formatValue(result, !traceOn);return "Resume with async result [" + formatted + "]";});invocableMethod = invocableMethod.wrapConcurrentResult(result);}invocableMethod.invokeAndHandle(webRequest, mavContainer); // 执行目标方法if (asyncManager.isConcurrentHandlingStarted()) {return null;}return getModelAndView(mavContainer, modelFactory, webRequest);}finally {webRequest.requestCompleted();}
}
// org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);setResponseStatus(webRequest);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;}
}
// org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite#handleReturnValue
@Override
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);// 返回值处理
}
// org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite#selectHandler
@Nullable
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;
}

3.返回值处理器

有许多返回值处理器

返回值处理器也是一个接口,HandlerMethodReturnValueHandler。

supportsReturnType来判断是否能处理该返回值类型。

handleReturnValue来真正处理返回值。

(1)、返回值处理器判断是否支持这种类型返回值 supportsReturnType

(2)、返回值处理器调用 handleReturnValue 进行处理

(3)、RequestResponseBodyMethodProcessor 可以处理返回值标了@ResponseBody 注解的。

4.SpringMVC到底支持哪些返回值

ModelAndView
Model
View // 视图
ResponseEntity
ResponseBodyEmitter
StreamingResponseBody // 流式数据
HttpEntity // 不能为RequestEntity
HttpHeaders
Callable// 支持异步
DeferredResult // 支持异步
ListenableFuture// 支持异步
CompletionStage
WebAsyncTask
方法有 @ModelAttribute 且为对象类型的
方法有@ResponseBody 注解 ---> RequestResponseBodyMethodProcessor;(自定义实体类响应json数据,使用此返回值处理器)

5.处理@ResponseBody 注解的RequestResponseBodyMethodProcessor

// 处理返回值
// 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 进行处理 将数据写为jsonwriteWithMessageConverters(returnValue, returnType, inputMessage, 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());}// 判断返回值是否是资源类型(stream流数据)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 = 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();// // 循环所有的消息转换器,SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter ,看谁能处理for (HttpMessageConverter<?> converter : this.messageConverters) { // 得到MappingJackson2HttpMessageConverter可以将对象写为json// 利用MappingJackson2HttpMessageConverter将对象转为json再写出去。最终 MappingJackson2HttpMessageConverter 把对象转为JSON(利用底层的jackson的objectMapper转换的)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(this.allSupportedMediaTypes);}
}

6.内容协商

内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型,请求头Accept)

7.HttpMessageConverter

(1)HttpMessageConverter实际是一个接口

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

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

canRead:是否能读(媒体类型参数)

canWrite:是否能写(媒体类型参数)

getSypportedMediaTypes:判断是否支持该类型的返回值

(2)默认的MessageConverter

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处理的

8.MappingJackson2HttpMessageConverter

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

// org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter#writeInternal
@Override
protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage)throws IOException, HttpMessageNotWritableException {MediaType contentType = outputMessage.getHeaders().getContentType();JsonEncoding encoding = getJsonEncoding(contentType);JsonGenerator generator = this.objectMapper.getFactory().createGenerator(outputMessage.getBody(), encoding);try {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 ?this.objectMapper.writerWithView(serializationView) : this.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);}
}
// org.springframework.http.converter.AbstractGenericHttpMessageConverter#write
@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() {@Overridepublic OutputStream getBody() {return outputStream;}@Overridepublic HttpHeaders getHeaders() {return headers;}}));}else {writeInternal(t, type, outputMessage);// 最终 MappingJackson2HttpMessageConverter 把对象转为JSON(利用底层的jackson的objectMapper转换的)outputMessage.getBody().flush();}
}

9.总结

标注@ResponseBody -> 使用RequestResponseBodyMethodProcessor处理 -> 查找对应的messageConverter处理返回值。

messageConverter可以处理多种返回值(不止局限于自定义的实体类,但是一定要加上@ResponseBody注解)

三、初识内容协商

1.什么是内容协商

内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型,请求头Accept)。

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

2.springboot响应xml报文

(1)引入支持xml依赖

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

(2)服务器会按照请求头来判断返回的内容格式

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

如果写application/json会返回json报文,如果写application/xml就会返回xml报文。

四、内容协商的原理

同上,标注@ResponseBody -> 使用RequestResponseBodyMethodProcessor处理 -> 查找对应的messageConverter处理返回值。

1.关键代码AbstractMessageConverterMethodProcessor

// AbstractMessageConverterMethodProcessor的writeWithMessageConverters
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,只要没加拦截器处理一般情况下都没有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();// 获取客户端(PostMan、浏览器)支持接收的内容类型。(获取客户端Accept请求头字段)【application/xml】// 底层其实就是使用原生request,调用getHeaderValues("Accept")List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);// 遍历循环所有当前系统的 MessageConverter,看谁支持操作这个对象(Person)// 找到支持操作Person的converter,把converter支持的媒体类型统计出来。(详见上面)// 导入了jackson处理xml的包,xml的converter就会自动进来List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);// 最终发现,客户端需要【application/xml】。服务端可以提供10种服务【json、xml】if (body != null && producibleTypes.isEmpty()) {throw new HttpMessageNotWritableException("No converter found for return value of type: " + valueType);}List<MediaType> mediaTypesToUse = new ArrayList<>();// 内容协商,双重for循环,找出最佳匹配媒体类型。最终将匹配的媒体类型放到mediaTypesToUsefor (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);// 选中的媒体类型只会有一个,有匹配的就break。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();// 找Converter,看谁能将对象转成需要的媒体类型(xml)// 用 支持 将对象转为 最佳匹配媒体类型 的converter。调用它进行转化 。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(this.allSupportedMediaTypes);}
}

2.各种服务器端支持的Converts

后四个分别支持对象转json、对象转xml。

其中对象转xml的因为导入了jackson的xml支持的包,会自动出现。

3.获取客户端(PostMan、浏览器)支持接收的内容类型

底层其实就是使用原生request,调用getHeaderValues("Accept")

// org.springframework.web.accept.HeaderContentNegotiationStrategy#resolveMediaTypes
@Override
public List<MediaType> resolveMediaTypes(NativeWebRequest request)throws HttpMediaTypeNotAcceptableException {String[] headerValueArray = request.getHeaderValues(HttpHeaders.ACCEPT);if (headerValueArray == null) {return MEDIA_TYPE_ALL_LIST;}List<String> headerValues = Arrays.asList(headerValueArray);try {List<MediaType> mediaTypes = MediaType.parseMediaTypes(headerValues);MediaType.sortBySpecificityAndQuality(mediaTypes);return !CollectionUtils.isEmpty(mediaTypes) ? mediaTypes : MEDIA_TYPE_ALL_LIST;}catch (InvalidMediaTypeException ex) {throw new HttpMediaTypeNotAcceptableException("Could not parse 'Accept' header " + headerValues + ": " + ex.getMessage());}
}

4.遍历服务器所有MessageConverter,看谁支持操作这个对象(Person)

// org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#getProducibleMediaTypes(javax.servlet.http.HttpServletRequest, java.lang.Class<?>, java.lang.reflect.Type)
protected List<MediaType> getProducibleMediaTypes(HttpServletRequest request, Class<?> valueClass, @Nullable Type targetType) {Set<MediaType> mediaTypes =(Set<MediaType>) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);if (!CollectionUtils.isEmpty(mediaTypes)) {return new ArrayList<>(mediaTypes);}else if (!this.allSupportedMediaTypes.isEmpty()) {List<MediaType> result = new ArrayList<>();for (HttpMessageConverter<?> converter : this.messageConverters) {if (converter instanceof GenericHttpMessageConverter && targetType != null) {if (((GenericHttpMessageConverter<?>) converter).canWrite(targetType, valueClass, null)) {result.addAll(converter.getSupportedMediaTypes());}}else if (converter.canWrite(valueClass, null)) {result.addAll(converter.getSupportedMediaTypes());}}return result;}else {return Collections.singletonList(MediaType.ALL);}
}

5.服务端支持的10种处理数据类型(MediaType)

6.权重优先匹配原则

浏览器q=0.9,权重为0.9,没有写支持json,而*/*权重为0.8,所以优先会返回xml格式。

7.开启基于请求参数的内容协商功能

(1)使用

浏览器内容协商优先支持xml,可以开启使用请求参数作为内容协商。

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

(2)原理

开启配置之后,内容协商管理器会注册一个使用参数的内容协商策略,参数名称就是format,format的值就支持xml和json。

底层实际上是使用request.getParameter("format");获取请求参数。

五、自定义MessageConverter

1.功能

实现多协议数据兼容。json、xml、test-测试报文类型

(1)、@ResponseBody 响应数据出去 调用 RequestResponseBodyMethodProcessor 处理

(2)、Processor 处理方法返回值。通过 MessageConverter 处理

(3)、所有 MessageConverter 合起来可以支持各种媒体类型数据的操作(读、写)

(4)、内容协商找到最终的 messageConverter;

2.实现

(1)添加自定义的MessageConverter

import com.cxf.model.Person;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;/**
* 自定义的Converter,操作Person类型的数据
*/
public class TestMessageConverter implements HttpMessageConverter<Person> {@Overridepublic boolean canRead(Class<?> clazz, MediaType mediaType) {return false; // 不需要支持读}@Overridepublic boolean canWrite(Class<?> clazz, MediaType mediaType) {return clazz.isAssignableFrom(Person.class); // 是Person类型就可以写}/*** 服务器要统计所有MessageConverter都能写出哪些内容类型* application/test*/@Overridepublic List<MediaType> getSupportedMediaTypes() {// 把字符串解析成媒体类型集合return MediaType.parseMediaTypes("application/test");}@Overridepublic Person read(Class<? extends Person> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {return null;}@Overridepublic 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());}
}

(2)在配置类中注册刚写的Converter.

//WebMvcConfigurer定制化SpringMVC的功能
@Bean
public WebMvcConfigurer webMvcConfigurer(){return new WebMvcConfigurer() {// 扩展MessageConverters@Overridepublic void extendMessageConverters(List<HttpMessageConverter<?>> converters) {converters.add(new TestMessageConverter());}};
}

3.测试

4.使用参数方式

(1)加配置

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

(2)配置类加配置

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.accept.HeaderContentNegotiationStrategy;
import org.springframework.web.accept.ParameterContentNegotiationStrategy;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;@Configuration(proxyBeanMethods = false)
public class WebConfig{//WebMvcConfigurer定制化SpringMVC的功能@Beanpublic WebMvcConfigurer webMvcConfigurer(){return new WebMvcConfigurer() {/*** 自定义内容协商策略,有可能我们添加的自定义的功能会覆盖默认很多功能,导致一些默认的功能失效。* @param configurer*/@Overridepublic void configureContentNegotiation(ContentNegotiationConfigurer configurer) {//Map<String, MediaType> mediaTypesMap<String, MediaType> mediaTypes = new HashMap<>();mediaTypes.put("json",MediaType.APPLICATION_JSON);mediaTypes.put("xml",MediaType.APPLICATION_XML);mediaTypes.put("test",MediaType.parseMediaType("application/test")); // 服务端可以产生test类型的数据//指定支持解析哪些参数对应的哪些媒体类型ParameterContentNegotiationStrategy parameterStrategy = new ParameterContentNegotiationStrategy(mediaTypes);parameterStrategy.setParameterName("ff"); // ff=testHeaderContentNegotiationStrategy headeStrategy = new HeaderContentNegotiationStrategy(); // 放一个基于请求头的,都可以使用configurer.strategies(Arrays.asList(parameterStrategy,headeStrategy));}// 扩展MessageConverters@Overridepublic void extendMessageConverters(List<HttpMessageConverter<?>> converters) {converters.add(new TestMessageConverter());}};}
}

(3)测试一下吧

springboot-springmvc响应json与xml原理-详解数据响应与内容协商(长文预警,收藏慢啃)相关推荐

  1. SpringMVC关于json、xml自动转换的原理研究[附带源码分析 --转

    SpringMVC关于json.xml自动转换的原理研究[附带源码分析] 原文地址:http://www.cnblogs.com/fangjian0423/p/springMVC-xml-json-c ...

  2. HTTP响应报文与工作原理详解

    HTTP 是一种请求/响应式的协议,即一个客户端与服务器建立连接后,向服务器发送一个请求;服务器接到请求后,给予相应的响应信息. 超文本传输协议(Hypertext Transfer Protocol ...

  3. 【转】HTTP响应报文与工作原理详解

    超文本传输协议(Hypertext Transfer Protocol,简称HTTP)是应用层协议.HTTP 是一种请求/响应式的协议,即一个客户端与服务器建立连接后,向服务器发送一个请求;服务器接到 ...

  4. 如何通过SpringMVC框架响应JSON数据

    1. 通过SpringMVC框架响应JSON数据 在SpringMVC框架(含SpringBoot框架)中,当处理请求的方法之前添加了@ResponseBody后,或在控制器类之前使用的是@RestC ...

  5. SpringMVC接受JSON参数详解及常见错误总结我改

    SpringMVC接受JSON参数详解及常见错误总结 最近一段时间不想使用Session了,想感受一下Token这样比较安全,稳健的方式,顺便写一个统一的接口给浏览器还有APP.所以把一个练手项目的前 ...

  6. SpringBoot使用教程【1】Restful API设计 返回json,xml格式...

    版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/qingfeng812/article/details/74738885 效果展示: 浏览器截图 ht ...

  7. SpringMVC教程--json使用详解

    json数据交互 1.1 @RequestBody 作用: @RequestBody注解用于读取http请求的内容(字符串),通过springmvc提供的HttpMessageConverter接口将 ...

  8. SpringBoot—整合log4j2入门和log4j2.xml配置详解

    关注微信公众号:CodingTechWork,一起学习进步. 引言   对于一个线上程序或者服务而言,重要的是要有日志输出,这样才能方便运维.而日志的输出需要有一定的规划,如日志命名.日志大小,日志分 ...

  9. SpringMVC的响应JSON数据和过滤静态资源

    1.过滤静态资源 @ResponseBody注解作用 作用:将方法的返回值,以特定的格式写入到response的body区域,进而将数据返回给客户端. 当方法上面没有写ResponseBody,底层会 ...

最新文章

  1. centos 安装 py pyhs2
  2. 自由缩放属性resize
  3. python numpy 子数组_Python利用Numpy数组进行数据处理(一)
  4. php程序yii是什么意思,yii是啥意思?
  5. 什么是线索评分?如何让线索评分发挥作用?
  6. Play on Words UVA - 10129 (欧拉回路)
  7. Ubuntu升级Ruby
  8. 从模板部署虚拟机和自定义虚拟机规范
  9. 小龙 Dev-C++ 5.16 发布了
  10. idea gradle GC overhead limit exceeded
  11. 自定义配置文件 /etc/httpd/conf.d
  12. 一生中必看的30个故事
  13. 如何恢复sd卡数据呢?
  14. 单例模式(饥汉式、懒汉式)
  15. 【论文阅读】Squeeze-and-Attention Networks for Semantic Segmentation(CVPR2020)
  16. 软考高项论文写作注意要点(一)
  17. 计算机往届生考研失败找工作,终于发现应届生和往届生考研复试会被歧视吗-考研复习...
  18. 基于ssm应届毕业生在线职位管理平台
  19. java数组length是下标_数组的下标与长度
  20. unity Text文字淡入效果

热门文章

  1. fatal: No configured push destination
  2. python30行代码_仅利用30行Python代码来展示X算法
  3. java用jsoup爬网页数据_java使用jsoup爬取网页数据
  4. git bash here创建项目无法选择m_你应该知道的10个Git命令
  5. afe模拟前端的重要性_UCD3138模拟前端(AFE)模块:模拟前端模块(AFE)简介
  6. 获取rabbitmq连接对象_RabbitMQ——简单队列
  7. 高基数特征的预处理方式
  8. spark端口实验总结:
  9. logistic回归--好文
  10. dropout的正则化理解