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

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

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

目录

一、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场景

    org.springframework.boot
    spring-boot-starter-web

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


  org.springframework.boot
  spring-boot-starter-json
  2.3.4.RELEASE
  compile
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 进行处理 将数据写为json
   writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
protected  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 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 acceptableTypes = getAcceptableMediaTypes(request); // 获取浏览器能接收的内容类型
      List producibleTypes = getProducibleMediaTypes(request, valueType, targetType); // 服务器能生产的内容类型

      if (body != null && producibleTypes.isEmpty()) {
         throw new HttpMessageNotWritableException(
               "No converter found for return value of type: " + valueType);
      }
      List 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>) 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 producibleMediaTypes =
            (Set) 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() {
         @Override
         public OutputStream getBody() {
            return outputStream;
         }
         @Override
         public 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依赖


    com.fasterxml.jackson.dataformat
    jackson-dataformat-xml

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

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

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

 

四、内容协商的原理

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

1.关键代码AbstractMessageConverterMethodProcessor
// AbstractMessageConverterMethodProcessor的writeWithMessageConverters
protected  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 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 acceptableTypes = getAcceptableMediaTypes(request);
      // 遍历循环所有当前系统的 MessageConverter,看谁支持 *** 作这个对象(Person)
      // 找到支持 *** 作Person的converter,把converter支持的媒体类型统计出来。(详见上面)
      // 导入了jackson处理xml的包,xml的converter就会自动进来
      List 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 mediaTypesToUse = new ArrayList<>();
      // 内容协商,双重for循环,找出最佳匹配媒体类型。最终将匹配的媒体类型放到mediaTypesToUse
      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);

      // 选中的媒体类型只会有一个,有匹配的就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>) 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 producibleMediaTypes =
            (Set) 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 resolveMediaTypes(NativeWebRequest request)
      throws HttpMediaTypeNotAcceptableException {

   String[] headerValueArray = request.getHeaderValues(HttpHeaders.ACCEPT);
   if (headerValueArray == null) {
      return MEDIA_TYPE_ALL_LIST;
   }

   List headerValues = Arrays.asList(headerValueArray);
   try {
      List 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 getProducibleMediaTypes(
      HttpServletRequest request, Class valueClass, @Nullable Type targetType) {

   Set mediaTypes =
         (Set) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
   if (!CollectionUtils.isEmpty(mediaTypes)) {
      return new ArrayList<>(mediaTypes);
   }
   else if (!this.allSupportedMediaTypes.isEmpty()) {
      List 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,而* public class TestMessageConverter implements HttpMessageConverter { @Override public boolean canRead(Class clazz, MediaType mediaType) { return false; // 不需要支持读 } @Override public boolean canWrite(Class clazz, MediaType mediaType) { return clazz.isAssignableFrom(Person.class); // 是Person类型就可以写 } @Override public List getSupportedMediaTypes() { // 把字符串解析成媒体类型集合 return MediaType.parseMediaTypes("application/test"); } @Override public Person read(Class 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()); } }

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

//WebMvcConfigurer定制化SpringMVC的功能
@Bean
public WebMvcConfigurer webMvcConfigurer(){
    return new WebMvcConfigurer() {

        // 扩展MessageConverters
        @Override
        public void extendMessageConverters(List> 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的功能
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {

            
            @Override
            public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
                //Map mediaTypes
                Map 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=test

                HeaderContentNegotiationStrategy headeStrategy = new HeaderContentNegotiationStrategy(); // 放一个基于请求头的,都可以使用

                configurer.strategies(Arrays.asList(parameterStrategy,headeStrategy));
            }

            // 扩展MessageConverters
            @Override
            public void extendMessageConverters(List> converters) {
                converters.add(new TestMessageConverter());
            }

        };
    }
}

(3)测试一下吧

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5686074.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存