1. 概述
本文檔描述瞭如何在 Spring MVC 項目中實現內容協商。
通常,確定請求媒體類型有三種方法:
- (已棄用) 在請求中使用 URL 後綴(擴展名)(例如 .xml/.json)
- 使用請求中的 URL 參數(例如 ?format=json)
- 使用請求中的 Accept 標頭
默認情況下,Spring 內容協商管理器將按照這種順序嘗試使用這三種策略。如果未啓用任何這些策略,則可以指定一個默認內容類型作為回退。
2. 內容協商策略
讓我們從必要的依賴項開始——我們正在處理 JSON 和 XML 表示形式,因此對於本文,我們將使用 Jackson 處理 JSON:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.17.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.2</version>
</dependency>
為了支持XML,我們可以使用JAXB、XStream或較新的Jackson-XML支持。
由於我們在另一篇關於HttpMessageConverters的文章中已經解釋了Accept請求頭的用法,讓我們深入探討前兩個策略。
3. URL 後綴策略
在 Spring Boot 2.6.x 版本中,請求路徑與註冊的 Spring MVC 處理器映射進行匹配的默認策略已從 AntPathMatcher 變為 PathPatternParser。
由於 PathPatternParser 不支持後綴模式匹配,因此在使用此策略之前,首先需要使用遺留路徑匹配器。
可以通過在 application.properties 文件中添加 spring.mvc.pathmatch.matching-strategy 來將默認值切換回 AntPathMatcher。
默認情況下,此策略已禁用,需要通過在 application.properties 文件中將 spring.mvc.pathmatch.use-suffix-pattern 設置為 true 來啓用它:
spring.mvc.pathmatch.use-suffix-pattern=true
spring.mvc.pathmatch.matching-strategy=ant-path-matcher啓用後,框架可以從 URL 中直接檢查路徑擴展名以確定輸出內容類型。
在進行配置之前,讓我們先來看一個示例。以下是在典型的 Spring 控制器中實現的簡單 API 方法:
@RequestMapping(
value = "/employee/{id}",
produces = { "application/json", "application/xml" },
method = RequestMethod.GET)
public @ResponseBody Employee getEmployeeById(@PathVariable long id) {
return employeeMap.get(id);
}
讓我們通過使用 JSON 擴展來指定資源的媒體類型來調用它:
curl http://localhost:8080/spring-mvc-basics/employee/10.json如果使用 JSON 擴展,我們可能會得到以下結果:
{
"id": 10,
"name": "Test Employee",
"contactNumber": "999-999-9999"
}以下是使用 XML 時的請求-響應示例:
curl http://localhost:8080/spring-mvc-basics/employee/10.xml響應體:
<employee>
<contactNumber>999-999-9999</contactNumber>
<id>10</id>
<name>Test Employee</name>
</employee>現在,如果未使用任何擴展程序或使用未配置的擴展程序,則將返回默認內容類型:
curl http://localhost:8080/spring-mvc-basics/employee/10現在我們來查看一下如何設置這個策略,包括使用 Java 和 XML 配置。
3.1. Java 配置文件
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(true).
favorParameter(false).
ignoreAcceptHeader(true).
useJaf(false).
defaultContentType(MediaType.APPLICATION_JSON);
}我們來詳細梳理一下。
首先,我們啓用了路徑擴展策略。 值得注意的是,自 Spring Framework 5.2.4 版本起,favorPathExtension(boolean) 方法已被棄用,以減少使用路徑擴展進行內容協商的趨勢。
然後,我們還禁用了 URL 參數策略和 Accept 頭部策略——因為我們只想依賴路徑擴展來確定內容的類型。
接下來,我們關閉了 Java 激活框架;JAF 可以用作備用機制來選擇輸出格式,如果傳入的請求與我們配置的任何策略不匹配,則使用它。我們關閉了它,因為我們將 JSON 設置為默認內容類型。請注意,自 Spring Framework 5 版本起,useJaf()方法已被棄用。
最後——我們將 JSON 設置為默認值。這意味着如果兩個策略均未匹配,所有傳入的請求都將映射到提供 JSON 內容的控制器方法。
3.2. XML 配置
讓我們快速查看相同的配置,僅使用 XML 格式:
<bean id="contentNegotiationManager"
class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
<property name="favorPathExtension" value="true" />
<property name="favorParameter" value="false"/>
<property name="ignoreAcceptHeader" value="true" />
<property name="defaultContentType" value="application/json" />
<property name="useJaf" value="false" />
</bean>
4. URL 參數策略
在上一部分中,我們使用了路徑擴展。現在,我們將配置 Spring MVC 以利用 URL 參數。
通過將 favorParameter 屬性的值設置為 true,可以啓用此策略。
下面我們來看一下它與我們之前示例的用法:
curl http://localhost:8080/spring-mvc-basics/employee/10?mediaType=json以下是 JSON 響應體的內容:
{
"id": 10,
"name": "Test Employee",
"contactNumber": "999-999-9999"
}如果使用 XML 參數,則輸出將以 XML 格式呈現。
curl http://localhost:8080/spring-mvc-basics/employee/10?mediaType=xml響應體:
<employee>
<contactNumber>999-999-9999</contactNumber>
<id>10</id>
<name>Test Employee</name>
</employee>現在我們來配置一下——首先使用Java,然後使用XML。
4.1. Java 配置文件
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false).
favorParameter(true).
parameterName("mediaType").
ignoreAcceptHeader(true).
useJaf(false).
defaultContentType(MediaType.APPLICATION_JSON).
mediaType("xml", MediaType.APPLICATION_XML).
mediaType("json", MediaType.APPLICATION_JSON);
}
讓我們仔細閲讀一下這個配置。
首先,當然,路徑擴展和 Accept 頭部策略已禁用(以及 JAF)。
其餘配置與之前相同。
4.2. 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="useJaf" value="false" />
<property name="mediaTypes">
<map>
<entry key="json" value="application/json" />
<entry key="xml" value="application/xml" />
</map>
</property>
</bean>同時,我們也可以同時啓用兩種策略(擴展和參數):
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(true).
favorParameter(true).
parameterName("mediaType").
ignoreAcceptHeader(true).
useJaf(false).
defaultContentType(MediaType.APPLICATION_JSON).
mediaType("xml", MediaType.APPLICATION_XML).
mediaType("json", MediaType.APPLICATION_JSON);
}在這種情況下,Spring 首先會查找路徑擴展名,如果路徑擴展名不存在,則會查找路徑參數。如果兩者都不可用,則會返回默認內容類型。
5. 使用 Accept 標頭策略
如果啓用了 Accept 標頭,Spring MVC 將根據傳入請求中的值來確定表示形式類型。
為了啓用此方法,必須將 ignoreAcceptHeader 的值設置為 false,並且同時禁用其他兩種策略,以確保我們僅依賴於 Accept 標頭。
5.1. Java 配置文件
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(true).
favorParameter(false).
parameterName("mediaType").
ignoreAcceptHeader(false).
useJaf(false).
defaultContentType(MediaType.APPLICATION_JSON).
mediaType("xml", MediaType.APPLICATION_XML).
mediaType("json", MediaType.APPLICATION_JSON);
}5.2. XML 配置
<bean id="contentNegotiationManager"
class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
<property name="favorPathExtension" value="true" />
<property name="favorParameter" value="false"/>
<property name="parameterName" value="mediaType"/>
<property name="ignoreAcceptHeader" value="false" />
<property name="defaultContentType" value="application/json" />
<property name="useJaf" value="false" />
<property name="mediaTypes">
<map>
<entry key="json" value="application/json" />
<entry key="xml" value="application/xml" />
</map>
</property>
</bean>最後,我們需要通過將插件插入整體配置中來啓用內容協商管理器。
<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager" />
6. 結論
一切都完成了。我們回顧了 Spring MVC 中內容協商的工作原理,並重點介紹瞭如何設置各種策略以確定內容類型。