专注综合财经股票网,提供炒股知识,追涨停技巧等文章,是广大股民的学习社区!

SpringMVC 使用 @ResponseBody 出406错误

发布:互联网2019-10-14 08:56:52分类: 国内资讯

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

开宗明义--解决办法:

1、请求路径不写后缀.html或写成.json2、必须写.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。哪里有错拜求指正^_^~

温馨提示如有转载或引用以上内容之必要,敬请将本文链接作为出处标注,谢谢合作!