1. 概述
本文介绍如何在 Spring MVC 项目中实现内容协商。
通常,可以通过三个选项来确定请求的媒体类型:
- (Deprecated) 在请求中使用 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.10.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.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 来启用它:
一旦启用,框架可以直接从 URL 检查路径扩展以确定输出内容类型。
spring.mvc.pathmatch.use-suffix-pattern=true
spring.mvc.pathmatch.matching-strategy=ant-path-matcher
在进入配置之前,让我们快速看一下一个示例。我们在典型的 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 header 策略——因为我们只想依靠路径扩展方式来确定内容的类型。
然后我们关闭 Java Activation Framework;如果传入请求与我们配置的任何策略都不匹配,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 以使用路径参数。
我们可以通过将 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 header 策略被禁用(以及 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 header 策略
如果启用了 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" />
六,结论
我们就完成了。我们研究了内容协商在 Spring MVC 中的工作原理,并重点介绍了一些设置它以使用各种策略来确定内容类型的示例。
本文的完整实现可以在 GitHub 上找到。