Spring MVC内容协商

2023/05/19

1. 概述

本文介绍如何在Spring MVC项目中实现Content Negotiation。

通常,有三种方式可以确定请求的媒体类型:

  • (已弃用)在请求中使用URL后缀(扩展名)(例如.xml/.json)
  • 在请求中使用URL参数(例如?format=json)
  • 在请求中使用Accept头

默认情况下,这是Spring Content Negotiation管理器尝试使用这三种策略的顺序。如果这些都未启用,我们可以指定默认内容类型。

2. Content Negotiation策略

首先我们需要添加必要的依赖,由于我们使用JSON和XML表示,因此在本文中,我们将使用Jackson来表示JSON:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.13.0</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.13.0</version>
</dependency>

3. URL后缀策略

在Spring Boot 2.6.x版本中,将请求路径与注册的Spring MVC HandlerMapping匹配的默认策略已从AntPathMatcher更改为PathPatternParser。

并且由于PathPatternParser不支持后缀模式匹配,所以在使用该策略之前,我们首先需要使用之前的路径匹配器,即AntPathMatcher。

我们可以在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方法实现:


@Controller
public class EmployeeController {

    Map<Long, Employee> employeeMap = new HashMap<>();

    @RequestMapping(value = "/employee/{Id}", produces = {"application/json", "application/xml"}, method = RequestMethod.GET)
    public @ResponseBody Employee getEmployeeById(@PathVariable final Long Id) {
        return employeeMap.get(Id);
    }
}

让我们通过使用JSON扩展来指定资源的媒体类型来调用它:

curl http://localhost:8080/spring-mvc-basics/employee/1.json

如果我们使用JSON扩展,会得到以下结果:

{
    "id": 1,
    "name": "John",
    "contactNumber": "223334411",
    "workingArea": "rh"
}

下面是使用XML的例子:

curl http://localhost:8080/spring-mvc-basics/employee/1.xml

响应体:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<employee>
    <contactNumber>223334411</contactNumber>
    <id>1</id>
    <name>John</name>
    <workingArea>rh</workingArea>
</employee>

如果我们不使用任何扩展名或使用未配置的扩展名,则将返回默认内容类型:

curl http://localhost:8080/spring-mvc-basics/employee/1

现在让我们看看如何同时使用Java和XML配置这个策略。

3.1 Java配置


@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        configurer.favorPathExtension(true).
                favorParameter(false).
                ignoreAcceptHeader(true).
                useJaf(false).
                defaultContentType(MediaType.APPLICATION_JSON);
    }
}

首先,我们启用路径扩展策略。值得一提的是,从Spring 5.2.4开始, 不推荐使用favoritePathExtension(boolean)方法,以阻止使用路径扩展进行Content Negotiation。

然后,我们禁用URL参数策略以及Accept头策略,因为我们只想依赖路径扩展方式来确定内容的类型。

然后我们关闭Java Activation Framework;如果传入的请求与我们配置的任何策略都不匹配,JAF可以用作选择输出格式的后备机制。 我们禁用它是因为我们要将JSON配置为默认内容类型。请注意,从Spring 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/1?mediaType=json

以下是JSON响应正文的内容:

{
    "id": 1,
    "name": "John",
    "contactNumber": "223334411",
    "workingArea": "rh"
}

如果我们使用XML参数,输出将是XML格式:

curl http://localhost:8080/spring-mvc-basics/employee/1?mediaType=xml

响应主体:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<employee>
    <contactNumber>223334411</contactNumber>
    <id>1</id>
    <name>John</name>
    <workingArea>rh</workingArea>
</employee>

4.1 Java配置


@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    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>

此外,我们可以同时启用两种策略:


@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    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配置


@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    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>

最后,我们需要通过将contentNegotiationManager添加到整体配置中来启用它:


<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager"/>

6. 总结

我们研究了Spring MVC中的Content Negotiation是如何工作的,并重点介绍了一些设置它以使用各种策略来确定内容类型的示例。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章