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 請求頭的用法,讓我們深入研究這兩種策略。
,而非 。
由於後綴模式匹配不支持 PathPatternParser,因此首先需要使用遺留路徑匹配器,然後再使用該策略。
可以通過在 application.properties 文件中添加 ,將默認策略切換回 。
默認情況下,此策略已禁用,需要通過在 application.properties 中設置 來啓用它:
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 配置。
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(true).
favorParameter(false).
ignoreAcceptHeader(true).
useJaf(false).
defaultContentType(MediaType.APPLICATION_JSON);
}
讓我們過一遍細節。
首先,我們啓用了路徑擴展策略。 此外,值得注意的是,自 Spring Framework 5.2.4 版本起,為了不鼓勵使用路徑擴展進行內容協商, 方法已棄用。
然後,我們還禁用了 URL 參數策略以及 頭部策略——因為我們只想依賴路徑擴展方式來確定內容類型。
我們關閉了 Java 激活框架;JAF 可用作備用機制來選擇輸出格式,如果傳入請求不匹配我們配置的任何策略,則可以用來選擇輸出格式。我們關閉了它,因為我們將 JSON 設置為默認內容類型。請注意, 方法自 Spring Framework 5 版本起已棄用。
最後——我們設置 JSON 為默認值。這意味着如果未匹配任何兩個策略,所有傳入請求都將映射到提供 JSON 內容的控制器方法。
讓我們也快速查看相同的配置,僅使用 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. The URL Parameter Strategy
我們之前在上一部分中使用了路徑擴展 – 現在讓我們設置 Spring MVC 以使用路徑參數。
我們可以通過將 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 Configuration
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 Configuration
<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 中內容協商的工作方式,並重點介紹瞭如何設置各種策略以確定內容類型。
。 來啓用它:spring.mvc.pathmatch.use-suffix-pattern=true
spring.mvc.pathmatch.matching-strategy=ant-path-matcher@RequestMapping(
value = "/employee/{id}",
produces = { "application/json", "application/xml" },
method = RequestMethod.GET)
public @ResponseBody Employee getEmployeeById(@PathVariable long id) {
return employeeMap.get(id);
}
curl http://localhost:8080/spring-mvc-basics/employee/10.json{
"id": 10,
"name": "Test Employee",
"contactNumber": "999-999-9999"
}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<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>
curl http://localhost:8080/spring-mvc-basics/employee/10?mediaType=json
{
"id": 10,
"name": "Test Employee",
"contactNumber": "999-999-9999"
}
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>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);
}
<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);
}
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);
}
<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" />