1. 概述

文档对于任何要共享给世界的代码都至关重要,尤其是当代码相对复杂时。优秀的 API 文档不仅能吸引开发者使用,更能体现产品质量。 如果一家公司的文档写得马马虎虎,其 API 的质量恐怕也堪忧。

但开发者都喜欢写机器能理解的代码,而不是给人看的文字。

本教程将探讨如何结合使用 Spring REST Docs 来同时编写文档和 API。我们将以查询参数文档化为例展开。

2. API 设计

考虑一个简单的单接口 API:

@RestController
@RequestMapping("/books")
public class BookController {
    private final BookService service;

    public BookController(BookService service) {
        this.service = service;
    }

    @GetMapping
    public List<Book> getBooks(@RequestParam(name = "page") Integer page) {
        return service.getBooks(page);
    }
}

这个接口返回网站可用的书籍集合。但由于书籍数量庞大,我们无法一次性返回所有数据。客户端需要提供目录页码,我们只返回对应页的数据。

我们决定将此参数设为必需(这是默认设置)。这样既能提升服务性能,又能防止客户端一次性请求过多数据。

但我们必须明确告知客户端这个规则:如果缺少参数,客户端将收到错误消息。

3. 文档化方案

传统文档化方式是重复劳动:开发者需要在代码中实现功能,又在文档中重复描述交互逻辑。这种方式既低效,又无法保证所有人都会遵守。

文档是追求清晰度的正式文件,不需要华丽的辞藻或创新的结构。既然如此,为什么不用代码生成文档呢? 这样既能避免重复劳动,又能确保文档与代码同步更新。

Spring REST Docs 正是为此而生。但它并非直接从代码生成文档(因为代码本身缺少上下文),而是从测试中生成。 这种方式能处理复杂的场景和示例,还有一个好处:测试失败时不会生成文档。

4. 带文档的测试

Spring REST Docs 支持主流的 REST 测试框架。 我们将以 MockMvcWebTestClientREST-assured 为例,但核心思路对所有框架都适用。

我们将使用 JUnit 5 作为测试基础,但也支持 JUnit 4

所有测试方法都需要添加以下扩展:

@ExtendWith({RestDocumentationExtension.class, SpringExtension.class})

这些是专门用于生成文档的特殊类。

4.1. WebTestClient

先从现代的 WebTestClient 开始。如前所述,需要扩展测试类并正确配置:

@BeforeEach
public void setUp(ApplicationContext webApplicationContext, RestDocumentationContextProvider restDocumentation) {
    this.webTestClient = WebTestClient.bindToApplicationContext(webApplicationContext)
      .configureClient()
      .filter(documentationConfiguration(restDocumentation))
      .build();
}

之后编写测试,既能验证 API 又能记录请求信息:

@Test
@WithMockUser
void givenEndpoint_whenSendGetRequest_thenSuccessfulResponse() {
    webTestClient.get().uri("/books?page=2")
      .exchange().expectStatus().isOk().expectBody()
      .consumeWith(document("books",
        requestParameters(parameterWithName("page").description("The page to retrieve"))));
}

4.2. WebMvcTest 和 MockMvc

这种方法与前一种非常相似,同样需要正确配置:

@BeforeEach
public void setUp(WebApplicationContext webApplicationContext, RestDocumentationContextProvider restDocumentation) {
    this.mockMvc = webAppContextSetup(webApplicationContext)
      .apply(documentationConfiguration(restDocumentation))
      .alwaysDo(document("{method-name}", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint())))
      .build();
}

测试方法结构相同,只是使用 MockMvc 的 API:

@Test
void givenEndpoint_whenSendGetRequest_thenSuccessfulResponse() throws Exception {
    mockMvc.perform(get("/books?page=2"))
      .andExpect(status().isOk())
      .andDo(document("books",
        requestParameters(parameterWithName("page").description("The page to retrieve"))));
}

4.3. REST-assured

最后看 REST-assured 的例子。由于需要运行服务器,不应使用 @WebMvcTest@AutoconfigureMockMvc 这里改用 @AutoconfigureWebMvc 并指定端口:

@BeforeEach
void setUp(RestDocumentationContextProvider restDocumentation, @LocalServerPort int port) {
    this.spec = new RequestSpecBuilder()
      .addFilter(documentationConfiguration(restDocumentation))
      .setPort(port)
      .build();
}

测试结构依然相似:

@Test
@WithMockUser
void givenEndpoint_whenSendGetRequest_thenSuccessfulResponse() {
    RestAssured.given(this.spec).filter(document("users", requestParameters(
        parameterWithName("page").description("The page to retrieve"))))
      .when().get("/books?page=2")
      .then().assertThat().statusCode(is(200));
}

5. 生成文档

现在虽然写了测试,但还没生成最终文档。需要完成以下步骤:

5.1. 生成的代码片段

运行测试后,可在 target 目录找到生成的片段。也可配置输出目录指定存储位置。片段内容类似这样:

[source,bash]
----
$ curl 'http://localhost:8080/books?page=2' -i -X GET
----

同时会生成包含参数说明的 .adoc 文件:

|===
|Parameter|Description

|`+page+`
|The page to retrieve

|===

5.2. 文档生成流程

下一步需要配置 AsciiDoc 将片段转换为可读的 HTML。AsciiDoc 是一种简洁强大的标记语言,可用于生成 HTML/PDF 或 写书

要生成 HTML 文档,需先定义模板:

= 使用 Spring REST Docs 的书籍文档

书店交互指南:

.request
include::{snippets}/books/http-request.adoc[]

.request-parameters
include::{snippets}/books/request-parameters.adoc[]

.response
include::{snippets}/books/http-response.adoc[]

这里使用简单格式,但可创建更精美的自定义格式。AsciiDoc 的灵活性为此提供了支持。

5.3. 生成的 HTML

正确配置后,可将生成目标绑定到 Maven 阶段:

<executions>
    <execution>
        <id>generate-docs</id>
        <phase>package</phase>
        <goals>
            <goal>process-asciidoc</goal>
        </goals>
        <configuration>
            <backend>html</backend>
            <doctype>book</doctype>
            <attributes>
                <snippets>${snippetsDirectory}</snippets>
            </attributes>
            <sourceDirectory>src/docs/asciidocs</sourceDirectory>
            <outputDirectory>target/generated-docs</outputDirectory>
        </configuration>
    </execution>
</executions>

运行 mvn 命令触发生成。前文定义的模板将渲染出如下 HTML:

请求参数文档示例

可将此流程集成到 CI/CD 管道中,确保文档始终最新准确。另一个好处是减少了易出错的手工劳动。

6. 总结

文档是软件的重要组成部分。开发者都明白这一点,但很少有人能坚持编写或维护。Spring REST Docs 让我们能基于代码(而非主观理解)轻松生成高质量文档。

✅ 核心优势:

  • 测试驱动生成,确保文档与实现同步
  • 支持复杂场景的文档化
  • 减少重复劳动,降低维护成本

⚠️ 踩坑提醒:

  • 务必正确配置输出目录和 Maven 插件
  • 测试失败时不会更新文档,需保证测试可靠性

本教程所有代码可在 GitHub 获取。


原始标题:Document Query Parameters with Spring REST Docs | Baeldung