背景
在使用飞书审批关联外部选项对接时发现始终保存说结构错误,于是使用postman进行测试发现了果然是结构问题,返回了一个xml
结构,所以在@PostMapping
添加了一下produces
然后问题就解决了,为什么需要去手动添加一下,才会输出json
格式呢
通过对SpringBoot框架源码调试,最终发现SpringBoot框架是在AbstractMessageConverterMethodProcessor
类中的writeWithMessageConverters()
方法中实现判断返回格式的。
@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;
// ... 部分省略代码
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();
// 获取调用方能接受什么类型的MediaType
List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);
// 获取服务提供方能产生哪些类型的MediaType
List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
if (body != null && producibleTypes.isEmpty()) {
throw new HttpMessageNotWritableException(
"No converter found for return value of type: " + valueType);
}
// 综合请求方和服务提供方的MediaType情况,计算最终能够返回哪些MediaType
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进行排序
MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
// 计算最终选择返回哪个MediaType,按照先后顺序,只要有一个符合条件,则直接返回,忽略剩余其他的可满足条件的MediaType
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);
}
}
// ...其他省略代码,包括最终的结果的返回
}
writeWithMessageConverters()
方法主要作用就是把接口返回的结果经过合适的Converter
处理之后再返回。
这就要求首先判断应该返回什么类型的MediaType
。
writeWithMessageConverters()
方法判断使用什么类型的MediaType
逻辑如下:
首先调用getAcceptableMediaTypes(request)
判断接收方能接受哪些类型的MediaType
。
如果没有设置的话,则按照默认的来。默认的MediaType为MEDIA_TYPE_ALL_LIST
List<MediaType> MEDIA_TYPE_ALL_LIST = Collections.singletonList(MediaType.ALL);
/**
* Public constant media type that includes all media ranges (i.e. "*/*").
*/
public static final MediaType ALL;
/**
* A String equivalent of {@link MediaType#ALL}.
*/
public static final String ALL_VALUE = "*/*";
接着调用getProducibleMediaTypes()
方法来计算当前接口能产生哪些类型的MediaType
。
/**
* Returns the media types that can be produced. The resulting media types are:
* <ul>
* <li>The producible media types specified in the request mappings, or
* <li>Media types of configured converters that can write the specific return value, or
* <li>{@link MediaType#ALL}
* </ul>
* @since 4.2
*/
@SuppressWarnings("unchecked")
protected List<MediaType> getProducibleMediaTypes(
HttpServletRequest request, Class<?> valueClass, @Nullable Type targetType) {
// 如果request中已经指定了MediaType,则直接使用指定的
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<>();
// 依次遍历当前系统中所有的HttpMessageConverte列表,只要能够支持写入指定的targetType,即认为可生成converter支持的MediaType
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);
}
}
getProducibleMediaTypes()
方法首先判断request
中有没有指定特定的MediaType
,如果有的话则直接使用指定的,如果没有的话,则依次遍历当前系统中所有的HttpMessageConverter
,只要对应Converter
的canWrite()
方法返回true
,则把对应Converter
所支持的所有的MediaType
加入返回列表中。
当前系统中支的所有HttpMessageConverter
列表如下:
执行完成发现第一个canWrite()
返回true
的Converter
是BczRequestConfig$HtmlJsonMessageConverter
。
执行完成之后的 result的结果如下图所示:
在计算得到所有可以生成的MediaType
之后,又会依次判断这些可以生成的MediaType
是否兼容acceptableTypes
,由于本次请求中acceptableTypes
为默认值,则默认兼容。
之后会把上一步中得到的所有的MediaType
按照各自的qualityValue(每个MediaType都会有一个值)
进行从小到大排序。
本系统中没有做任何特殊的设置,默认值都是1,所有MediaType
顺序保持不变。
做完上述操作之后,从上一步中处理之后的所有MediaType
中选择第一个确定的MediaType
(所谓的确定的MediaType是指该MediaType对应的type和subtype都是具体的,不存在通配符的)作为该次请求应该返回的MediaType
for (MediaType mediaType : mediaTypesToUse) {
if (mediaType.isConcrete()) {
selectedMediaType = mediaType;
break;
}
else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {
selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
break;
}
}
位于列表中第一个的MediaType
为application/xml;charset=UTF-8
,符合条件,所以application/xml;charset=UTF-8
即被认为只接口请求最终返回的MediaType
。
那么如何去解决呢?
解决
既然我们知道 application/xml;charset=UTF-8
排在第一位,只有将application/json
排在application/xml;charset=UTF-8
之前就可以解决其问题,所以我们添加一个HttpMessageConverter
,令他排在第一位就可以解决
-
@Override public void extendMessageConverters(List<org.springframework.http.converter.HttpMessageConverter<?>> converters) { converters.add(0,new MappingJackson2HttpMessageConverter(mapper)); }
-
可以设置请求头的接受类型
accpet
HttpServletRequest request = inputMessage.getServletRequest(); List<MediaType> acceptableTypes; try { //请求的 accept type acceptableTypes = getAcceptableMediaTypes(request); } catch (HttpMediaTypeNotAcceptableException ex) { int series = outputMessage.getServletResponse().getStatus() / 100; if (body == null || series == 4 || series == 5) { if (logger.isDebugEnabled()) { logger.debug("Ignoring error response content (if any). " + ex); } return; } throw ex; }