HttpMediaTypeNotAcceptableException: Could not find acceptable representation原因

这个异常是什么意思?

返回值无法确定返回类型.

这个异常出自: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版本的问题?

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值