Java sping boot 从1.x升级到2.x后原本返回xml格式的数据变成json原因分析(AbstractMessageConverterMethodProcessor源码分析)

问题描述:
项目sping boot 从1.5.10升级到2.3.8后,同一个接口原本是直接能返回xml格式的数据,但现在变成json字符串格式了。

环境说明:
老的
sping boot 1.5.10
sping-webmvc 4.3.14
新的
sping boot 2.3.8
sping-webmvc 5.2.12(最终原因就是新版本的webmvc有变动)

解决方案:
1.sping boot 2.3.8中引用sping-webmvc 4.3.14的包
2.需要返回xml的接口上增加produces = MediaType.APPLICATION_ATOM_XML_VALUE

问题发现步骤:

1.sping boot 1.5.10下调试代码分析

以下是在sping boot 1.5.10版本中找到的根据路径获取接口的MediaType方法。可以在AbstractMessageConverterMethodProcessor类中writeWithMessageConverters方法上打上断点,这样就可以直接进行调试,跳过前面的代码。
在这里插入图片描述
在这里插入图片描述
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
*/
@SuppressWarnings("unchecked")
protected <T> void writeWithMessageConverters(T value, MethodParameter returnType,
      ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
      throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {


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


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


   HttpServletRequest request = inputMessage.getServletRequest();
   List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);
   List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);


   if (outputValue != null && producibleMediaTypes.isEmpty()) {
      throw new IllegalArgumentException("No converter found for return value of type: " + valueType);
   }


   Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
   for (MediaType requestedType : requestedMediaTypes) {
      for (MediaType producibleType : producibleMediaTypes) {
         if (requestedType.isCompatibleWith(producibleType)) {
            compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType));
         }
      }
   }
   if (compatibleMediaTypes.isEmpty()) {
      if (outputValue != null) {
         throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
      }
      return;
   }


   List<MediaType> mediaTypes = new ArrayList<MediaType>(compatibleMediaTypes);
   MediaType.sortBySpecificityAndQuality(mediaTypes);


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


   if (selectedMediaType != null) {
      selectedMediaType = selectedMediaType.removeQualityValue();
      for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
         if (messageConverter instanceof GenericHttpMessageConverter) {
            if (((GenericHttpMessageConverter) messageConverter).canWrite(
                  declaredType, valueType, selectedMediaType)) {
               outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
                     (Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
                     inputMessage, outputMessage);
               if (outputValue != null) {
                  addContentDispositionHeader(inputMessage, outputMessage);
                  ((GenericHttpMessageConverter) messageConverter).write(
                        outputValue, declaredType, selectedMediaType, outputMessage);
                  if (logger.isDebugEnabled()) {
                     logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +
                           "\" using [" + messageConverter + "]");
                  }
               }
               return;
            }
         }
         else if (messageConverter.canWrite(valueType, selectedMediaType)) {
            outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
                  (Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
                  inputMessage, outputMessage);
            if (outputValue != null) {
               addContentDispositionHeader(inputMessage, outputMessage);
               ((HttpMessageConverter) messageConverter).write(outputValue, selectedMediaType, outputMessage);
               if (logger.isDebugEnabled()) {
                  logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +
                        "\" using [" + messageConverter + "]");
               }
            }
            return;
         }
      }
   }


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

通过request来获取MediaType集合

List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);

在这里插入图片描述

private List<MediaType> getAcceptableMediaTypes(HttpServletRequest request) throws HttpMediaTypeNotAcceptableException {
    List<MediaType> mediaTypes = this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request));
    return (mediaTypes.isEmpty() ? Collections.singletonList(MediaType.ALL) : mediaTypes);
}

根据内容协商管理器
ContentNegotiationManager 类中的resolveMediaTypes方法获取本次请求的MediaType

@Override
public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
   for (ContentNegotiationStrategy strategy : this.strategies) {
      List<MediaType> mediaTypes = strategy.resolveMediaTypes(request);
      if (mediaTypes.isEmpty() || mediaTypes.equals(MEDIA_TYPE_ALL)) {
         continue;
      }
      return mediaTypes;
   }
   return Collections.emptyList();
}

具体通过strategy.resolveMediaTypes(request); 进入WebMvcAutoConfiguration类下的resolveMediaTypes方法

@Override
public List<MediaType> resolveMediaTypes(NativeWebRequest webRequest)
      throws HttpMediaTypeNotAcceptableException {
   Object skip = webRequest.getAttribute(SKIP_ATTRIBUTE,
         RequestAttributes.SCOPE_REQUEST);
   if (skip != null && Boolean.parseBoolean(skip.toString())) {
      return Collections.emptyList();
   }
   return this.delegate.resolveMediaTypes(webRequest);
}

根据 return this.delegate.resolveMediaTypes(webRequest);
进入AbstractMappingContentNegotiationStrategy类中resolveMediaTypes方法

@Override
public List<MediaType> resolveMediaTypes(NativeWebRequest webRequest)
        throws HttpMediaTypeNotAcceptableException {


    return resolveMediaTypeKey(webRequest, getMediaTypeKey(webRequest));
}

通过getMediaTypeKey(webRequest)方法,进入到PathExtensionContentNegotiationStrategy

@Override
protected String getMediaTypeKey(NativeWebRequest webRequest) {
   HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
   if (request == null) {
      logger.warn("An HttpServletRequest is required to determine the media type key");
      return null;
   }
   String path = this.urlPathHelper.getLookupPathForRequest(request);
   String extension = UriUtils.extractFileExtension(path);
   return (StringUtils.hasText(extension) ? extension.toLowerCase(Locale.ENGLISH) : null);
}

在这里插入图片描述

2.sping boot 2.3.8下调试代码分析

在这里插入图片描述

/**
* 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 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();
        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);
    }
}

看着有变动,大部分都差不多,还是根据

List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);

来获取MediaType集合

@Override
public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
    for (ContentNegotiationStrategy strategy : this.strategies) {
        List<MediaType> mediaTypes = strategy.resolveMediaTypes(request);
        if (mediaTypes.equals(MEDIA_TYPE_ALL_LIST)) {
            continue;
        }
        return mediaTypes;
    }
    return MEDIA_TYPE_ALL_LIST;
}

问题就出在strategy.resolveMediaTypes(request);

sping boot 1.5.10版本会进入WebMvcAutoConfiguration(自动配置类)下resolveMediaTypes方法,
在这里插入图片描述
在这里插入图片描述
然后进入AbstractMappingContentNegotiationStrategy类中resolveMediaTypes方法,
最后通过getMediaTypeKey(webRequest)方法,进入到PathExtensionContentNegotiationStrategy,然后根据路径获取关键字xml。

而sping boot 2.3.8版本strategy.resolveMediaTypes(request); 直接进入的是ContentNegotiationStrategy接口的实现类HeaderContentNegotiationStrategy中,通过request中的accept
去获取MediaType了,所以取不到,返回的默认值 /
在这里插入图片描述
在这里插入图片描述
ContentNegotiationStrategy类是根据request来解析出对应的MediaType的,他有如下几种类:
1.FixedContentNegotiationStrategy:固定类型解析
2.HeaderContentNegotiationStrategy:根据request中的accept解析
3.ParameterContentNegotiationStrategy:根据reques中的参数解析
4.PathExtensionContentNegotiationStrategy:根据请求的路径来解析

所以现在就是因为1.5.10默认使用PathExtensionContentNegotiationStrategy去取MediaType了,而2.3.8默认使用HeaderContentNegotiationStrategy去取MediaType。
跟踪下代码验证下:

1.5.10中可以看到this.contentNegotiationManager加载的解析类有PathExtensionContentNegotiationStrategy和HeaderContentNegotiationStrategy,而且PathExtensionContentNegotiationStrategy在前。
在这里插入图片描述
2.3.8中只有HeaderContentNegotiationStrategy
在这里插入图片描述
根本原因就是1.5.10初始化AbstractMessageConverterMethodProcessor是加入了PathExtensionContentNegotiationStrategy。
在这里插入图片描述
而2.3.8中没有加入
在这里插入图片描述
所以新版本需要在接口上主动设置MediaType
在这里插入图片描述

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值