1. 概述
文檔是構建 REST API 的關鍵組成部分。在本教程中,我們將探討 SpringDoc,它簡化了基於 OpenAPI 3 規範為 Spring Boot 3.x 應用生成和維護 API 文檔的過程。
2. 設置 springdoc-openapi
Spring Boot 3.x 需要使用 版本 2 的 springdoc-openapi:
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.8.5</version>
</dependency>進一步,Springdoc OpenAPI 版本必須與 Spring Boot 版本兼容,如Springdoc 官方兼容矩陣所示兼容矩陣。
2.1. OpenAPI 描述路徑
在正確設置依賴後,我們可以運行我們的應用程序,並在 /v3/api-docs</em/> 中找到 OpenAPI 描述。這是默認路徑:
http://localhost:8080/v3/api-docs進一步,我們還可以通過使用 springdoc.api-docs 屬性在 application.properties 中自定義路徑。例如,我們可以將路徑設置為 /api-docs:
springdoc.api-docs.path=/api-docs然後,我們就可以訪問文檔:
http://localhost:8080/api-docs默認情況下,OpenAPI 定義是採用 JSON 格式。對於 yaml 格式,可以在以下位置獲取定義:
http://localhost:8080/api-docs.yaml3. 與 Swagger UI 集成
除了生成 OpenAPI 3 規範,我們還可以將 springdoc-openapi 集成到 Swagger UI 中,以便與我們的 API 規範進行交互並測試端點。
springdoc-openapi 依賴項已經包含 Swagger UI,因此我們可以通過以下網址訪問 API 文檔:
http://localhost:8080/swagger-ui/index.html3.1. 支持 swagger-ui 屬性
springdoc-openapi 庫也支持 swagger-ui 屬性。 這些屬性可以作為 Spring Boot 屬性使用,並帶有前綴 springdoc.swagger-ui。
例如,我們可以通過修改 application.properties 文件中的 springdoc.swagger-ui.path 屬性來自定義 API 文檔的路徑:
springdoc.swagger-ui.path=/swagger-ui-custom.html現在,我們的API文檔將提供在 http://localhost:8080/swagger-ui-custom.html。
例如,我們可以使用 springdoc.swagger-ui.operationsSorter 屬性根據HTTP方法對API路徑進行排序。
springdoc.swagger-ui.operationsSorter=method3.2. 示例 API
假設我們的應用程序有一個用於管理 Book 對象的控制器:
@RestController
@RequestMapping("/api/book")
public class BookController {
@Autowired
private BookRepository repository;
@GetMapping("/{id}")
public Book findById(@PathVariable long id) {
return repository.findById(id)
.orElseThrow(() -> new BookNotFoundException());
}
@GetMapping("/")
public Collection<Book> findBooks() {
return repository.getBooks();
}
@PutMapping("/{id}")
@ResponseStatus(HttpStatus.OK)
public Book updateBook(
@PathVariable("id") final String id, @RequestBody final Book book) {
return book;
}
}
然後,當我們運行我們的應用程序時,我們可以查看文檔在:
http://localhost:8080/swagger-ui-custom.html讓我們深入到 /api/book 端點,查看其請求和響應的詳細信息:
4. 與 springdoc-openapi 集成 Spring WebFlux
我們還可以為 Spring WebFlux 應用程序啓用 springdoc.swagger-ui 屬性。 這允許我們輕鬆地在 Spring WebFlux 應用程序中集成 springdoc-openapi 和 Swagger UI。 要啓用此功能,我們將在 pom.xml 文件中添加 springdoc-openapi-webflux-ui 依賴項。
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
<version>2.8.5</version>
</dependency>5. 使用標籤進行組織
OpenAPI 規範使用標籤將 API 分組到不同的類別和組中。每個 API 操作可以有多個標籤,Swagger UI 將根據這些標籤將操作分組顯示。
我們可以定義 Swagger 的 openapi.yaml 定義文件中標籤。為了演示如何排序標籤,讓我們使用在方法和類級別上的 @Tag 註解來指定它們。
讓我們以書籍 API 為例,並在控制器類和其方法上指定標籤:
@Tag(name = "tag_at_class_level", description = "Books related class level tag")
public class BooksController {
@Tag(name = "create")
@Tag(name = "common_tag_at_method_level")
@Tag(name = "createBook")
@PostMapping(path = "/book")
@io.swagger.v3.oas.annotations.parameters.RequestBody(required = true)
public Book book(@Valid @RequestBody Book book) {
return book;
}
@Tag(name = "find")
@Tag(name = "common_tag_at_method_level")
@Tag(name = "findBook", description = "Find Books related tag")
@GetMapping(path = "/findBookById")
public List findById(@RequestParam(name = "id", required = true)
@NotNull @NotBlank @Size(max = 10) long id) {
List bookList = new ArrayList<>();
Book book = new Book();
book.setId(1);
bookList.add(book);
return bookList;
}
@Tag(name = "delete")
@Tag(name = "common_tag_at_method_level")
@Tag(name = "deleteBook")
@DeleteMapping(path = "/deleteBookById")
public long deleteById(@RequestParam(name = "id", required = true)
@NotNull @NotBlank @Size(max = 10) long id) {
return id;
}
@Tag(name = "update")
@Tag(name = "common_tag_at_method_level")
@Tag(name = "updateBook")
@PutMapping(path = "/updateBookById")
public long updateById(@RequestParam(name = "id", required = true)
@NotNull @NotBlank @Size(max = 10) long id) {
return id;
}
}5.1. 默認標籤順序
當運行應用程序時,Swagger UI (http://localhost:8080/swagger-ui.html) 會按照其默認顯示順序顯示標籤,並且某些附加條件會優先生效:
默認顯示順序基於操作名稱: PUT, POST, GET, DELETE,按該順序排列。換句話説,PUT 操作相關的標籤會在 POST 操作相關的標籤之前顯示,以此類推。
原因是 tag_at_class_level 位於第一個標籤是因為 @Tag 註解會將標籤添加到 openAPI.tags 字段,從而在應用了 @Tag 註解(例如,帶有 description 或 externalDocs 等附加字段)時,確定全局標籤排序。同樣,findBook 標籤列在第二個位置。但是,如果僅提供 name,則標籤僅應用於操作,不會影響全局標籤排序。
此外,每個操作都會添加一個類級別標籤,這也是為什麼 tag_at_class_level 標籤應用於每個操作的原因。當標籤應用於多個操作時,這些操作按 PUT, POST, GET, 和 DELETE 順序排列。
正如排序所示,PUT 相關的標籤會在全局優先的標籤之後排列。讓我們顯示其他標籤,以發現 common_tag_at_method_level 位於下一個位置,因為它是一個 PUT 相關的標籤:
在 PUT 相關的標籤之後,POST 相關的標籤按順序排列。讓我們顯示剩餘的標籤,以發現剩餘的 GET 和 DELETE 相關的標籤按該順序排列:
如上所示,Swagger UI 的默認顯示順序可能不符合我們的需求。 鑑於此,讓我們看看如何控制此顯示順序的一些方法。
5.2. 使用 tagsSorter 屬性
首先,我們可以通過在 Spring 屬性文件中聲明一個屬性來配置排序,例如在 application.properties 中:
springdoc.swagger-ui.tagsSorter=alpha該屬性指示 Swagger UI 在其顯示中按標籤組的字母順序進行排序。
讓我們再次運行相同的應用程序並啓動 Swagger UI。 這一次,標籤的排序是基於標籤名稱的字母順序:
再次,當一個標籤應用於多個操作時,該標籤下的操作按 PUT, POST, GET, 和 DELETE 順序排列。
為了驗證基於標籤名稱的字母順序排序,讓我們同時顯示剩餘的標籤:
或者,我們可以將該屬性的值設置為一個排序函數。
5.3. 使用 writer-with-order-by-keys 屬性
或者,我們也可以使用 springdoc.writer-with-order-by-keys 屬性來指定標籤的排序,該屬性也位於 application.properties 中:
springdoc.writer-with-order-by-keys=true此屬性具有獨特性,因為它不特定於 Swagger UI。它基於路徑按字母順序排序來確定標籤順序。 但是,它仍然尊重openAPI.tags 字段中定義的全局標籤,並在所有標籤之前列出它們。 讓我們通過運行應用程序並啓動 Swagger UI 來驗證排序:
在優先顯示的全局標籤之後,它按路徑字母順序對標籤進行排序。 因此,它首先列出與 /book 路徑關聯的操作的標籤,然後是與 /deleteBookById 路徑關聯的操作的標籤:
讓我們列出剩餘的標籤以驗證標籤按路徑名稱排序,並按字母順序排列:
5.4. 使用全局 標籤 部分
此外,我們還可以使用全局 標籤 部分在我們的 openapi.yaml 定義文件的根級別中定義標籤的排序順序:
tags:
- name: books
description: Everything about your books
externalDocs:
url: http://docs.my-api.com/books.htm
- name: create
description: Add a book to the inventory
externalDocs:
url: http://docs.my-api.com/add-book.htm這告訴 Swagger UI 首先顯示 books 標籤,然後顯示 create 標籤。當我們的訂單不是按字母順序排列,或者無法通過排序函數輕鬆描述時,這非常實用。
或者,我們可以使用 @OpenAPIDefinition 註解,在規範級別上程序化地設置這些標籤。讓我們使用相同的控制器類並使用 @OpenAPIDefinition 註解:
@OpenAPIDefinition(tags = {
@Tag(name = "create", description = "Add book to inventory"),
@Tag(name = "delete", description = "Delete book from inventory"),
@Tag(name = "find", description = "Find book from inventory"),
@Tag(name = "update", description = "Update book in inventory"),
@Tag(name = "createBook", description = "Add book to inventory"),
@Tag(name = "deleteBook", description = "Delete book from inventory"),
@Tag(name = "findBook", description = "Find book from inventory"),
@Tag(name = "updateBook", description = "Update book in inventory")
})
public class BooksController_2 { ... }讓我們再次運行該應用程序並啓動 Swagger UI。 Swagger UI 會按照聲明的順序顯示這些標籤:
讓我們列出剩餘的標籤以驗證它們是否按我們指定的順序排列:
值得注意的是,當我們使用 @OpenAPIDefinition 註解時,我們不使用任何屬性來排序標籤。
6. 暴露分頁信息
Spring Data JPA 與 Spring MVC 集成非常順暢。其中一個示例就是 Pageable 支持:
@GetMapping("/filter")
public Page<Book> filterBooks(@ParameterObject Pageable pageable) {
return repository.getBooks(pageable);
}Pageable 的支持自 springdoc-openapi v1.6.0 版本起就已內置提供。生成的文檔中會添加 page、size 和 sort 查詢參數。
7. 使用 springdoc-openapi Maven 插件
springdoc-openapi 庫提供 Maven 插件 springdoc-openapi-maven-plugin,它可以生成 OpenAPI 描述文件,格式為 JSON 和 YAML。
springdoc-openapi-maven-plugin 插件與 spring-boot-maven 插件一起工作。 Maven 在 integration-test 階段運行 openapi 插件。
下面我們來看如何在我們的 pom.xml 中配置插件:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>3.4.3</version>
<executions>
<execution>
<id>pre-integration-test</id>
<goals>
<goal>start</goal>
</goals>
</execution>
<execution>
<id>post-integration-test</id>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-maven-plugin</artifactId>
<version>1.4</version>
<executions>
<execution>
<phase>integration-test</phase>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>我們還可以配置插件使用自定義值:
<plugin>
<executions>
.........
</executions>
<configuration>
<apiDocsUrl>http://localhost:8080/v3/api-docs</apiDocsUrl>
<outputFileName>openapi.json</outputFileName>
<outputDir>${project.build.directory}</outputDir>
</configuration>
</plugin>
讓我們更詳細地瞭解一下可以配置的插件參數:
- apiDocsUrl – 文檔訪問的 JSON 格式 URL,默認值為 http://localhost:8080/v3/api-docs
- outputFileName – 存儲定義的文件的名稱;默認值為 openapi.json
- outputDir – 文檔存儲的絕對路徑;默認情況下,它為 ${project.build.directory}
8. 使用 JSR-303 Bean 驗證自動生成文檔
當我們的模型包含 JSR-303 Bean 驗證註解,例如 @NotNull、@NotBlank、@Size、@Min 和 @Max 時,springdoc-openapi 庫會利用這些註解來為相應的約束生成額外的模式文檔。
讓我們通過使用我們的 Book 實體來舉例説明:
public class Book {
private long id;
@NotBlank
@Size(min = 0, max = 20)
private String title;
@NotBlank
@Size(min = 0, max = 30)
private String author;
}現在,用於 Book 豆的生成文檔更加具有信息量:
9. 使用 @ControllerAdvice 和 @ResponseStatus 生成文檔
使用 @ResponseStatus 在 @RestControllerAdvice 類的方法中,將自動生成響應代碼的文檔。 在此 @RestControllerAdvice 類中,兩個方法都使用了 @ResponseStatus 註解:
@RestControllerAdvice
public class GlobalControllerExceptionHandler {
@ExceptionHandler(ConversionFailedException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseEntity<String> handleConversion(RuntimeException ex) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(BookNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ResponseEntity<String> handleBookNotFound(RuntimeException ex) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
}
}因此,我們現在可以看到對響應碼 400 和 404 的文檔。
10. 使用 @Operation 和 @ApiResponses 生成文檔
接下來,讓我們看看如何使用 OpenAPI 特定的註解為我們的 API 添加描述。
要做到這一點,我們將使用 @Operation 和 @ApiResponses 註解來標註控制器的 /api/book/{id} 端點:
@Operation(summary = "Get a book by its id")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Found the book",
content = { @Content(mediaType = "application/json",
schema = @Schema(implementation = Book.class)) }),
@ApiResponse(responseCode = "400", description = "Invalid id supplied",
content = @Content),
@ApiResponse(responseCode = "404", description = "Book not found",
content = @Content) })
@GetMapping("/{id}")
public Book findById(@Parameter(description = "id of book to be searched")
@PathVariable long id) {
return repository.findById(id).orElseThrow(() -> new BookNotFoundException());
}以下是效果:
如我們所見,添加到 @Operation 的文本位於 API 操作級別。 類似地,添加到各種 @ApiResponse 元素中的 @ApiResponses 容器註解中的描述也在此處可見,從而賦予我們的 API 響應意義。
我們沒有獲取響應 400 和 404 的 Schema。 因為我們定義了空的 @Content 用於它們,所以只顯示它們的描述。
11. 描述請求體
當定義接受請求體(request body)的端點時,我們可以使用 OpenAPI 註解中的 <em/>@RequestBody</em/> 來在 API 文檔中描述預期的請求體內容。
例如,如果我們將一個 `Book</em/> 對象傳遞給我們的 API 以創建或更新書籍,我們可以對請求體進行標註以進行文檔記錄:
@Operation(summary = "Create a new book")
@ApiResponses(value = {
@ApiResponse(responseCode = "201", description = "Book created successfully",
content = { @Content(mediaType = "application/json",
schema = @Schema(implementation = Book.class)) }),
@ApiResponse(responseCode = "400", description = "Invalid input provided") })
@PostMapping("/")
public Book createBook(@io.swagger.v3.oas.annotations.parameters.RequestBody(
description = "Book to create", required = true,
content = @Content(mediaType = "application/json",
schema = @Schema(implementation = Book.class),
examples = @ExampleObject(value = "{ \"title\": \"New Book\", \"author\": \"Author Name\" }")))
@RequestBody Book book) {
return repository.save(book);
}此配置提供了請求體詳細文檔,包括示例和模式驗證,並允許API消費者更好地理解需要發送的數據。此外,我們注意到代碼中使用了兩個@RequestBody註解:
- Spring 的@RequestBody:這是在Spring Boot應用程序中使端點功能正常工作的必需條件,因為它將HTTP請求體綁定到方法參數上
- OpenAPI 的@RequestBody:這在OpenAPI定義中記錄了請求體,併為API消費者提供了詳細描述、模式驗證和示例
最後,讓我們查看此請求的生成文檔:
我們可以看到,註解中的具體信息會包含在文檔中,例如描述、示例對象和其他元數據,這些元數據位於OpenAPI@RequestBody註解中。
12. Kotlin 支持
Spring Boot 2.x 對 Kotlin 提供了原生支持。由於我們使用的是 Spring Boot 3.x 版本,SpringDoc 支持以 Kotlin 編寫的應用程序。
我們將創建一個簡單的 Foo API,以展示其功能。
在初始設置之後,我們將添加一個數據類和一個控制器。我們將它們放在 Boot App 的子包中,以便在運行時,它能夠拾取我們的 FooController 以及早期的 BookController。
@Entity
data class Foo(
@Id
val id: Long = 0,
@NotBlank
@Size(min = 0, max = 50)
val name: String = ""
)
@RestController
@RequestMapping("/")
class FooController() {
val fooList: List = listOf(Foo(1, "one"), Foo(2, "two"))
@Operation(summary = "Get all foos")
@ApiResponses(value = [
ApiResponse(responseCode = "200", description = "Found Foos", content = [
(Content(mediaType = "application/json", array = (
ArraySchema(schema = Schema(implementation = Foo::class)))))]),
ApiResponse(responseCode = "400", description = "Bad request", content = [Content()]),
ApiResponse(responseCode = "404", description = "Did not find any Foos", content = [Content()])]
)
@GetMapping("/foo")
fun getAllFoos(): List = fooList
}現在,當我們訪問 API 文檔 URL 時,我們還會看到 Foo API。
為了增強 Kotlin 類型支持,我們可以添加以下依賴項:
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-common</artifactId
<version>2.8.5</version>
</dependency>在那之後,我們的 Foo 模式將更加具有信息量,正如我們在添加 JSR-303 Bean 驗證時所見:
13. 結論
在本文中,我們學習瞭如何在我們的項目中設置 springdoc-openapi。然後,我們看到了如何將 springdoc-openapi 與 Swagger UI 集成。最後,我們還看到了如何在 Spring Webflux 項目中使用它。
接下來,我們使用 springdoc-openapi Maven 插件為我們的 API 生成 OpenAPI 定義,並看到了如何從 Spring Data 中暴露分頁和排序信息。之後,我們研究了 springdoc-openapi 如何使用 JSR 303 bean 驗證註解和 註解在 類中自動生成文檔。
我們還學習瞭如何使用一些 OpenAPI 專用的註解為我們的 API 添加描述。最後,我們對 OpenAPI 的 Kotlin 支持進行了瞭解。
springdoc-openapi 按照 OpenAPI 3 規範生成 API 文檔。 此外,它還為我們處理了 Swagger UI 的配置,使 API 文檔生成成為一項相對簡單的任務。