美文网首页首页投稿(暂停使用,暂停投稿)
SpringMVC 使用 @ResponseBody 出406错

SpringMVC 使用 @ResponseBody 出406错

作者: steamed_bun | 来源:发表于2017-09-07 15:46 被阅读0次

    感谢一波~:网上流传的两种方法,如此文章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
    哪里有错拜求指正^_^~

    相关文章

      网友评论

        本文标题:SpringMVC 使用 @ResponseBody 出406错

        本文链接:https://www.haomeiwen.com/subject/adfkjxtx.html