【转】SpringMVC 使用 @ResponseBody 出406错误

开宗明义–解决办法:

1、请求路径不写后缀.html或写成.json
2、必须写.html就做如下配置:

<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager" />
<!--.html为后缀名访问,默认返回数据类型是 text/html, 所以要修改返回的数据类型 -->
<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
    <property name="mediaTypes">
        <map>
            <entry key="html" value="application/json;charset=UTF-8"/>
        </map>
    </property>
</bean>

3、如果在@RequestMapping写了produces,必须写成application/json, 如下:

@RequestMapping(value = "/testJson",produces = "application/json;charset=UTF-8")

4、 在spring的配置文件中加入,注意加入命名空间

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <mvc:annotation-driven/>

</beans>

详解:

一、发现问题
前端页面:
注意:url访问是有后缀.html的

var user1 = {
    "userId":1,
    "userName":"abc"
};
$.ajax({
    type: "POST",
    url: 'testJson.html',
    data : JSON.stringify(user1),
    dataType:"json",
    contentType : 'application/json',
    success: function(data){
        console.log(data);
    },
    error: function(res){
        console.log(res);
        console.log("fail");
    },
});

后台controller:

 @ResponseBody
    @RequestMapping(value = "/testJson", produces = "application/json;charset=utf-8")
    public User testJson(HttpServletRequest request,@RequestBody User user){
        System.out.println(user);
        return user;
    }

测试结果:会发现返回的应该是 contentType : 'application/json;charset=utf-8'
但是却如下图…
在这里插入图片描述
二、查找原因
根据上面文章的提示,找到 AbstractMessageConverterMethodProcessor 类的 getAcceptableMediaTypes 方法,再进入resolveMediaTypes 方法:
debug,查看:

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

        for (ContentNegotiationStrategy strategy : this.strategies) {
            //用来解析Request Headers 的Accept到底是什么格式
            List<MediaType> mediaTypes = strategy.resolveMediaTypes(request);
            if (mediaTypes.isEmpty() || mediaTypes.equals(MEDIA_TYPE_ALL)) {
                continue;
            }
            return mediaTypes;
        }
        return Collections.emptyList();
    }

MEDIA_TYPE_ALL 对应的值为 */*
上面代码的解析的方法是:
第一次是根据请求的后缀解析,会进入AbstractMappingContentNegotiationStrategy的resolveMediaTypes方法:

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

        //getMediaTypeKey(webRequest)是根据请求url获得其后缀
        return resolveMediaTypeKey(webRequest, getMediaTypeKey(webRequest));
    }

然后调用同类下的resolveMediaTypes:

    public List<MediaType> resolveMediaTypeKey(NativeWebRequest webRequest, String key)
            throws HttpMediaTypeNotAcceptableException {

        if (StringUtils.hasText(key)) {
            MediaType mediaType = lookupMediaType(key);
            if (mediaType != null) {
                handleMatch(key, mediaType);
                return Collections.singletonList(mediaType);
            }
            mediaType = handleNoMatch(webRequest, key);
            if (mediaType != null) {
                addMapping(key, mediaType);
                return Collections.singletonList(mediaType);
            }
        }
        return Collections.emptyList();
    }

得知是 MediaType mediaType = lookupMediaType(key); 将后缀转换的,继续看 MappingMediaTypeFileExtensionResolver类下的lookupMediaType方法:

    protected MediaType lookupMediaType(String extension) {
        return this.mediaTypes.get(extension.toLowerCase(Locale.ENGLISH));
    }

可以发现是从mediaTypes 中直接获得的,找到 mediaTypes 会发现MappingMediaTypeFileExtensionResolver的构造器在最初就往mediaTypes 里面写入key-value:

private final ConcurrentMap<String, MediaType> mediaTypes =
            new ConcurrentHashMap<String, MediaType>(64);

    /**
     * Create an instance with the given map of file extensions and media types.
     * 使用给定的文件扩展名和媒体类型的映射创建一个实例。
     */
    public MappingMediaTypeFileExtensionResolver(Map<String, MediaType> mediaTypes) {
        if (mediaTypes != null) {
            for (Entry<String, MediaType> entries : mediaTypes.entrySet()) {
                String extension = entries.getKey().toLowerCase(Locale.ENGLISH);
                MediaType mediaType = entries.getValue();
                this.mediaTypes.put(extension, mediaType);
                this.fileExtensions.add(mediaType, extension);
                this.allFileExtensions.add(extension);
            }
        }
    }

得到的效果就是:
在这里插入图片描述
回到最上面的方法,由于解析出来的不为空也不为 */*,所以直接返回了
由上可以得出第一条解决办法的后半部分写成 .json

继续往下看,如果不写后缀的话,会发现第一次按照后缀解析返回值为空,会进行第二次解析,看代码发现是按照请求头的Accept解析,其解析方法调用的与第一次不同,为HeaderContentNegotiationStrategy类下的resolveMediaTypes方法:

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

        String header = request.getHeader(HttpHeaders.ACCEPT);
        if (!StringUtils.hasText(header)) {
            return Collections.emptyList();
        }
        try {
            List<MediaType> mediaTypes = MediaType.parseMediaTypes(header);
            MediaType.sortBySpecificityAndQuality(mediaTypes);
            return mediaTypes;
        }
        catch (InvalidMediaTypeException ex) {
            throw new HttpMediaTypeNotAcceptableException(
                    "Could not parse 'Accept' header [" + header + "]: " + ex.getMessage());
        }
    }

获取Accept:
在这里插入图片描述
备注:如果不设置请求头的Accept值得话,浏览器会自动加上:

chrome默认是 application/json, text/javascript, */*; q=0.01
最后被解析如下:
在这里插入图片描述
使用Postman测试,给Accept设置为 application/json:
在这里插入图片描述
解析就只有 application/json:
在这里插入图片描述
得到第一条解决办法的前半部分不写.html

第二种解决办法,就是将.html为后缀名的访问返回的数据类型修改为application/json
哪里有错拜求指正_~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值