SpringMVC 使用 @ResponseBody 出406错误

感谢一波~:网上流传的两种方法,如此文章Spring MVC Rest服务 返回json报406错误的解决办法,对我的项目并不起作用,直到看到一篇文章SpringMVC使用了@ResponseBody报406错误的问题(1),只是我的还有一点细微差别。

开宗明义--解决办法:

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);
            }
        }
    }

得到的效果就是:

mediaTypes

回到最上面的方法,由于解析出来的不为空也不为 */*,所以直接返回了
由上可以得出第一条解决办法的后半部分写成.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:

postman测试

解析就只有application/json

postman测试结果

得到第一条解决办法的前半部分不写.html


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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,539评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,594评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,871评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,963评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,984评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,763评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,468评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,357评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,850评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,002评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,144评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,823评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,483评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,026评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,150评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,415评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,092评论 2 355

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,672评论 18 139
  • Spring的模型-视图-控制器(MVC)框架是围绕一个DispatcherServlet来设计的,这个Servl...
    alexpdh阅读 2,647评论 0 3
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,189评论 25 707
  • 迷茫时就该读书。我说真的,因为你一定会从书中找到与你有同样感受的人,并且能从他的经验得到一些启发,让你有一种醍醐灌...
    许念一呀阅读 561评论 0 1
  • 妈妈,你睡过了头 听不见孩子的呼唤 看不见泣泪的震颤 妈妈,你睡过了头 疼惜辛勤操劳的你 再不见往日的身影 妈妈,...
    圩原君阅读 199评论 0 1