1. 概述

在这个教程中,我们将探讨如何定义自定义媒体类型,并通过Spring REST控制器来生成它们。使用自定义媒体类型的一个好用例是API版本控制。

2. API - 版本1

让我们从一个简单的例子开始:一个通过id暴露单个资源的API。

我们首先提供API的第一个版本,为此,我们将使用自定义HTTP头——"application/vnd.baeldung.api.v1+json"。客户端将通过Accept头请求这个自定义媒体类型。

这里是我们简单的端点:

@RequestMapping(
  method = RequestMethod.GET, 
  value = "/public/api/items/{id}", 
  produces = "application/vnd.baeldung.api.v1+json"
)
@ResponseBody
public BaeldungItem getItem( @PathVariable("id") String id ) {
    return new BaeldungItem("itemId1");
}

注意这里的produces参数,它指定了此API能够处理的自定义媒体类型。

现在,我们的BaeldungItem资源只有一个字段——itemId

public class BaeldungItem {
    private String itemId;
    
    // standard getters and setters
}

最后,为这个端点编写一个集成测试:

@Test
public void givenServiceEndpoint_whenGetRequestFirstAPIVersion_then200() {
    given()
      .accept("application/vnd.baeldung.api.v1+json")
    .when()
      .get(URL_PREFIX + "/public/api/items/1")
    .then()
      .contentType(ContentType.JSON).and().statusCode(200);
}

3. API - 版本2

假设我们需要更改向客户端暴露的资源详情。之前我们只暴露原始id,现在可能需要隐藏它并改为暴露名称,以增加灵活性。

重要的是要理解这个改变不具有向后兼容性,基本上是一个破坏性变更。

这是新的资源定义:

public class BaeldungItemV2 {
    private String itemName;

    // standard getters and setters
}

因此,我们需要将API迁移到第二个版本。我们将通过创建新的自定义媒体类型版本并定义一个新的端点来实现这一点:

@RequestMapping(
  method = RequestMethod.GET, 
  value = "/public/api/items/{id}", 
  produces = "application/vnd.baeldung.api.v2+json"
)
@ResponseBody
public BaeldungItemV2 getItemSecondAPIVersion(@PathVariable("id") String id) {
    return new BaeldungItemV2("itemName");
}

这样,我们就有了同一个端点,但可以处理新的V2操作。当客户端请求"application/vnd.baeldung.api.v1+json"时,Spring会代理到旧操作,而客户端将收到包含itemId字段的BaeldungItem(V1)。

但是,当客户端设置Accept头为"application/vnd.baeldung.api.v2+json"时,他们将正确地触发新操作,并得到包含itemName字段的资源(V2):

@Test
public void givenServiceEndpoint_whenGetRequestSecondAPIVersion_then200() {
    given()
      .accept("application/vnd.baeldung.api.v2+json")
    .when()
      .get(URL_PREFIX + "/public/api/items/2")
    .then()
      .contentType(ContentType.JSON).and().statusCode(200);
}

请注意,测试虽然相似,但使用了不同的Accept头。

4. 类级自定义媒体类型

最后,让我们讨论类级别的媒体类型定义——这也是可行的:

@RestController
@RequestMapping(
  value = "/", 
  produces = "application/vnd.baeldung.api.v1+json"
)
public class CustomMediaTypeController

如预期的那样,@RequestMapping注解在类级别上同样工作,并允许我们指定valueproducesconsumes参数。

5. 总结

本文示例了在版本化公开API时,定义自定义媒体类型的情况。所有这些示例和代码片段的实现可以在GitHub项目中找到。