開宗明義–解決辦法:
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
。
哪裏有錯拜求指正_~