这个异常是什么意思?
返回值无法确定返回类型.
这个异常出自:org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor的protected <T> void writeWithMessageConverters(T value, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)方法里.
核心代码如下:
protected <T> void writeWithMessageConverters(T value, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
...
...
HttpServletRequest request = inputMessage.getServletRequest();
List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);
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;
}...
...
if (outputValue != null) {
throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
}
}
可以看到,爆出异常的地方有两个,但一般情况下都是第一个地方爆出异常
解释下这段代码的意思:
getAcceptableMediaTypes是获取到请求类型,比如请求头里的Accept:text/html啊什么的。
getProducibleMediaTypes是获取方法的返回类型,
比如带有@ResponseBody的话,那就是application/json。
比如@RequestMapping(value="/index",produces="application/xml")这个,那就是application/xml
然后接下来中间那段的双重for循环就是来匹配请求类型和返回类型,spring当然期望请求类型和返回类型是一致的,是有交集的,如果有交集的话,它放到了一个新的集合里。
再接下来判断这个新的集合是不是空的,如果是空的,就代表着请求类型和返回类型不匹配,没有交集,无法确认返回什么类型,就爆了这个异常。
这样就很好理解了,我们只要注意请求类型和返回类型就可以了。当然可以,但里面还有一个小坑。我们深入一下:
追踪getAcceptableMediaTypes进去,最终到了org.springframework.web.accept.ContentNegotiationManager的resolveMediaTypes()方法
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();
}
这段代码的意思是:
我现在要拿到请求类型,怎么办呢?
使用我的解析策略类的集合,遍历他们,依次让他们去解析,如果解析到了就返回,如果解析不到,换下一个。
但现在有个比较坑的地方是,这个解析策略类的集合的顺序是什么?因为它的顺序决定着结果(一旦有结果就返回了),也决定着是否爆异常。
我们可以看到其中的一个解析策略类PathExtensionContentNegotiationStrategy它的解析原理是通过url进行解析:
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);
}
它会把xxx.html解析成“html”。会把xxx.json解析成“json”。但如果是xxx就无法解析,换下一个解析器了。
那么我碰到这个异常的情况是什么样的呢?
方法上写的是这个@ResponseBody@RequestMapping(value="/index.html")
那么我在访问的时候,它的解析策略类排在第一位的就是这个pathurl解析类,结果解析成html.
而返回类型解析因为@ResponseBody自然是application/json。
所以没有交集,自然报错。
但如果我把url的html去掉就没问题了,@ResponseBody@RequestMapping(value="/index")
因为排在第一位pathurl解析类解析不了,交给下一个了,下一个解析类是个默认的解析类,里面包括了一个*/*
这个*/*可以匹配任何的,所以就有交集了。也就不爆这个异常了。
更为深入的话我也没看,到此结束。以后有机会再看。
但我好像记得以前写过类似的,但不报错,是不是spring版本的问题?