在SpringMVC中,我們訪問一個RESTful @ReponseBody接口時,spring可以實現根據path extension來給出不同的響應格式,如:
// Json
curl http://localhost:8080/springautowired/foo.json
{"foo":"bar"}
// Xml
curl http://localhost:8080/springqutowired/foo.xml
<?xml version="1.0" encoding="UTF-8"?>
<root><foo>bar</foo></root>
這是spring內容協商的一種外在表現形式。
內容協商,ContentNegotiation 在spring中關注的核心問題是:你想要什麼樣格式的數據。你想要什麼,可以理解爲,http請求響應時,服務器應該往http response body裏寫什麼格式的數據。
分層概念
在SpringMVC的概念中,View指的是數據的展現形式,Model可以理解成領域數據模型或者載體,MVC的核心是分層,分層的目的是爲了解耦。就View與Model之間的解耦,指的是數據本身與數據展示形式的解耦,在spring應用內部,領域數據的載體,可以簡單理解爲bean。到了View層,數據的展現形式多種多樣,可以是Jsp, velocity,pdf 或者RESTful的json, xml等。
使用SpringMVC時,通常有兩種方式產生輸出:
使用RESTful 的 @ResponseBody,藉助於HttpMessageConverter來輸出像Json Xml等類型的的數據
使用 view resolution,可以生成更傳統的Html頁面(Jsp、Velocity等)
無論用哪種方式,你都有可能需要把Controller返回的相同的數據內容轉成不同的表現形式。
在之前的內容中有提到HttpMessageConverter 是spring處理rest請求時負責解析與轉換http消息的邏輯單元。藉助HttpMessageConverter,sping可以實現應用領域數據bean與http消息body的轉換。那麼問題來了,當前一個http請求完成時,該用哪個具體的HttpMessageConverter實現來回寫數據,這就是ContentNegotiation關注的內容。
協商策略
提到協商,必須有協商的點和策略,對應一個http請求,spring來判定請求中htto body格式主要通過以下三個因素(優先級同順序):
path extension 也稱爲path suffix 就是文章開頭提到的例子中的url後綴 .json .xml
url parameter 是一個明確指定媒體類型的參數,即通過顯式的參數告訴服務器,我想要什麼格式的數據。如format=json, 參數名可指定,默認是format
http header ( Accept) 如果上邊兩項都沒有,能參考http header Accept來判定響應數據格式,因某些瀏覽器或者有些http請求不完全按規則來指定需要的媒體類型,所以使用時需要謹慎使用header
上述協商的規則,在spring中被抽象爲接口:
// A strategy for resolving the requested media types
// for a request.
ContentNegotiationStrategy
FixContentNegotiationStrategy一般用來處理默認數據格式
HeaderContentNegotiationStrategy用來處理http Accept header的方式
ParameterContentNegotiationStrategy用來處理顯式的媒體類型參數方式
PathExtensionContentNegotiationStrategy處理url後綴方式
上邊這些strategy實現類以組合模式的形式封裝成ContentNegotiationManager,對外提供邏輯接口。
ContentNegotiationManager的resolveMediaTypes方法如下:
@Override
public List<MediaType> resolveMediaTypes(NativeWebRequest request)
throws HttpMediaTypeNotAcceptableException {
for (ContentNegotiationStrategy strategy : this.strategies) {
List<MediaType> mediaTypes = strategy.resolveMediaTypes(request);
if (mediaTypes.isEmpty() || mediaTypes.equals(MEDIA_TYPE_ALL)) {
continue;
}
return mediaTypes;
}
return Collections.emptyList();
}
使用方式
在項目中,可以通過ContentNegotiationManagerFactoryBean來配置一個全局的內容協商contentNegotiationManager,給spring MVC使用,ContentNegotiationManagerFactoryBean中主要屬性如下:
public class ContentNegotiationManagerFactoryBean
implements FactoryBean<ContentNegotiationManager>,
ServletContextAware, InitializingBean {
//是否關注url路徑後綴
private boolean favorPathExtension = true;
//是否關注媒體參數
private boolean favorParameter = false;
//是否忽略http Accept header
private boolean ignoreAcceptHeader = false;
// url 後綴與媒體類型的映射
private Map<String, MediaType> mediaTypes =
new HashMap<String, MediaType>();
//忽略未知的後綴
private boolean ignoreUnknownPathExtensions = true;
// 默認的媒體參數名
private String parameterName = "format”;
可以這樣定義一個contentNegotiationManager:
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false).
favorParameter(true).
parameterName("mediaType").
ignoreAcceptHeader(true)
defaultContentType(MediaType.APPLICATION_JSON).
mediaType("xml", MediaType.APPLICATION_XML).
mediaType("json", MediaType.APPLICATION_JSON);
}
}
xml形式的配置:
<bean id="contentNegotiationManager"
class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
<property name="favorPathExtension" value="false" />
<property name="favorParameter" value="true" />
<property name="parameterName" value="mediaType" />
<property name="ignoreAcceptHeader" value="true"/>
<property name="defaultContentType" value="application/json" />
<property name="mediaTypes">
<map>
<entry key="json" value="application/json" />
<entry key="xml" value="application/xml" />
</map>
</property>
</bean>
ContentNegotiation不僅限於Rest風格的http請求,SpringMVC中還有ContentNegotiatingViewResolver完成了viewResolver部分的內容協商功能,有興趣的小夥伴可以查看部分的源碼。
以上便是今天的全部內容,感謝關注,歡迎小夥伴留言反饋。
往期回顧:
SpringAutowired
長按,識別二維碼,加關注