【轉】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
哪裏有錯拜求指正_~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章