詳解 Spring Boot 3.x 中使用 SpringDoc 2 / Swagger3
大家好,我是碼哥,《Redis 高手心法》作者。
SpringBoot 已經成為 Java 開發的首選框架,今天碼哥跟大家聊一聊 Spring Boot3 如何與 Swagger3 集成打造一個牛逼轟轟的接口文檔。
為什么要用 Swagger
唐二婷:我最討厭兩件事:
- 別人接口不寫注釋;
- 自己寫接口注釋。
我們都被接口文檔折磨過,前端抱怨后端的接口文檔一坨屎;后端覺得寫接口文檔浪費時間。
每個項目都有成百上千個接口調用,這時候再要求人工編寫接口文檔并且保證文檔的實時更新幾乎是一件不可能完成的事,所以這時候我們迫切需要一個工具,一個能幫我們自動化生成接口文檔以及自動更新文檔的工具。
它就是 Swagger。
Swagger 的核心思想是通過定義和描述 API 的規范、結構和交互方式,以提高 API 的可讀性、可靠性和易用性,同時降低 API 開發的難度和開發者之間的溝通成本。
這里我采用了 Swagger3.0(Open API 3.0)的方式集成到 SpringBoot。springfox-boot-start 和 springfox-swagger2 都是基于 Swagger2.x 的。
這里將介紹 springdoc-openapi-ui,它是 SpringBoot 基于 Open API 3.0(Swagger3.0)
SpringFox 與 Swagger 的關系
Springfox 是一套可以幫助 Java 開發者自動生成 API 文檔的工具,它是基于 Swagger 2.x 基礎上開發的。
除了集成 Swagger 2.x,Springfox 還提供了一些額外功能,例如自定義 Swagger 文檔、API 版本控制、請求驗證等等。
但是隨著時間的推移,Swagger2.x 終究成為歷史,所以我們可以看出 springfox-boot-starter 的坐標從 3.0.0 版本(2020 年 7 月 14 日)開始就一直沒有更新;
也得注意的是 springfox-swagger2 坐標和 springfox-boot-start 是一樣的,但 springfox-boot-start 只有 3.0.0 版本。這里我就不在使用 Swagger2.x 版本
SpringDoc(推薦)
SpringDoc 對應坐標是 springdoc-openapi-ui,它是一個集成 Swagger UI 和 ReDoc 的接口文檔生成工具,在使用上與 springfox-boot-starter 類似,但提供了更為靈活、功能更加強大的工具。
其中除了可以生成 Swagger UI 風格的接口文檔,還提供了 ReDoc 的文檔渲染方式,可以自動注入 OpenAPI 規范的 JSON 描述文件,支持 OAuth2、JWT 等認證機制,并且支持全新的 OpenAPI 3.0 規范。
SpringBoot 3 集成 Swagger3.0
唐二婷:開干吧,Spring Boot3 如何集成這么吊炸天的工具。
需要注意的是,我們一般不會選擇原生的 Swagger maven 坐標來集成 Swagger。而是通過 springdoc-openapi-ui 的 Maven 坐標。
它可以很好的和 Spring 或 SpringBoot 項目集成;這個坐標也被 Spring 社區廣泛支持和認可,并被認為是集成 Swagger UI 和 OpenAPI 規范的一個優秀選擇。
引入 Maven
在該示例中,我使用 Spring Boot 3.0.2 集成 Swagger 3.0。
springdoc-openapi-starter-webmvc-ui:目前最新版本是 2.6.0,適用于 Spring Boot 3.x 和 Spring Framework 6。支持 Jakarta 命名空間(例如,jakarta.validation),適合 Spring Boot 3 的 Jakarta EE 轉換。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
配置 SwaggerOpenApiConfig
我們通過配置類的方式創建一個 OpenAPI 的 Bean 對象就可以創建 Swagger3.0 的文檔說明。
import io.swagger.v3.oas.models.ExternalDocumentation;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Swagger3Config {
@Bean
public OpenAPI springShopOpenAPI() {
return new OpenAPI()
.info(new Info().title("碼哥跳動 Swagger3 詳解")
.description("Swagger3 Spring Boot 3.0 application")
.version("v0.0.1")
.license(new License().name("Apache 2.0").url("http://springdoc.org")))
.externalDocs(new ExternalDocumentation()
.description("swagger 3 詳解")
.url("https://springshop.wiki.github.org/docs"));
}
}
OpenAPI 對象是 Swagger 中的核心類之一,用于描述整個 API 的結構和元數據。
Swagger2 和 Swagger3 使用的是完全不同的兩套注解,所以原本使用 Swagger2 相關注解的代碼頁需要完全遷移,改為使用 Swagger3 的注解。
Swagger2 | Swagger3 |
@Api | @Tag |
@ApiOperation | @Operation |
@ApiImplicitParams | @Parameters |
@ApiImplicitParam | @Parameter |
@ApiModel | @Schema |
@ApiModelProperty | @Schema |
@ApiResponses | @ApiResponses |
@ApiResponse | @ApiResponse |
@ApiIgnore | @Hidden 或者 其他注解的 hidden = true 屬性 |
配置文件
通過以下配置來控制 swagger 的開關和訪問地址:WEB 界面的顯示基于解析 JSON 接口返回的結果, 如果 api-docs 關閉, swagger-ui 即使 enable 也無法使用。
server:
port: 8013
spring:
application:
name: magebyte-swagger
springdoc:
api-docs:
enabled: true # 開啟OpenApi接口
path: /v3/api-docs # 自定義路徑,默認為 "/v3/api-docs"
swagger-ui:
enabled: true # 開啟swagger界面,依賴OpenApi,需要OpenApi同時開啟
path: /swagger-ui.html # 自定義路徑,默認為"/swagger-ui/index.html"
# Packages to include,多個用 , 分割
packagesToScan: zero.magebyte.magebyte.swagger.controller
需要注意的是,packagesToScan 用于指定 Controller 接口包路徑。
@Schema
Swagger3 用 @Schema 注解對象和字段, 以及接口中的參數類型。
@Setter
@Getter
@Schema(description = "響應返回數據對象")
public class Result<T> implements Serializable {
@Schema(
title = "code",
description = "響應碼",
format = "int32",
requiredMode = Schema.RequiredMode.REQUIRED
)
private Integer code;
@Schema(
title = "msg",
description = "響應信息",
accessMode = Schema.AccessMode.READ_ONLY,
example = "成功或失敗",
requiredMode = Schema.RequiredMode.REQUIRED
)
private String message;
@Schema(title = "data", description = "響應數據", accessMode = Schema.AccessMode.READ_ONLY)
private T data;
}
返回對象定義。
@Data
@AllArgsConstructor
@Schema(title = "學生模型VO", description = "響應視圖學生模型VO")
public class StudentVO implements Serializable {
@Schema(name = "學生ID", description = "學生ID屬性", format = "int64", example = "1")
private Long id; // 學生ID
@Schema(name = "學生姓名", description = "學生姓名屬性", example = "jack")
private String name; // 學生姓名
@Schema(name = "學生年齡", description = "學生年齡屬性", format = "int32", example = "24")
private Integer age; // 學生年齡
@Schema(name = "學生地址", description = "學生地址屬性", example = "安徽合肥")
private String address; // 學生地址
@Schema(name = "學生分數", description = "學生分數屬性", format = "double", example = "55.50")
private Double fraction; // 學生分數
@Schema(name = "學生愛好", description = "學生愛好屬性(List類型)",
type = "array", example = "[\"玩\", \"寫字\"]")
private List<String> likes; // 學生愛好
}
@Paramete
@Parameter 注解用于描述方法參數。如果不希望顯示某個參數, 用@Parameter(hidden = true)
修飾。
@Parameters({
@Parameter(name = "currentPage", description = "當前頁碼", required = true),
@Parameter(name = "size", description = "當前頁大小", example = "10"),
@Parameter(name = "queryUser", description = "用戶查詢條件")
})
Controller 接口定義
啟動項目,打開鏈接:http://localhost:8013/swagger-ui/index.html
@RestController
@RequestMapping("/students")
@Tag(name = "StudentControllerAPI", description = "學生控制器接口"
, externalDocs = @ExternalDocumentation(description = "這是一個接口文檔介紹"))
public class StudentController {
@Operation(
summary = "根據Id查詢學生信息", description = "根據ID查詢學生信息,并返回響應結果信息",
parameters = {
@Parameter(name = "id", description = "學生ID", required = true, example = "1")
},
responses = {
@ApiResponse(
responseCode = "200",
description = "響應成功",
content = @Content(
mediaType = "application/json",
schema = @Schema(
title = "Resul和StudentVO組合模型",
description = "返回實體,AjaxResult內data為StudentVO模型",
anyOf = {Result.class, StudentVO.class}
)
)
),
@ApiResponse(
responseCode = "500",
description = "響應失敗",
content = @Content(
mediaType = "application/json",
schema = @Schema(
title = "Resul模型",
description = "返回實體,Result內 data為空",
implementation = Result.class
)
)
)
}
)
@GetMapping("/{id}")
public Result<StudentVO> findOneStudent(@PathVariable(value = "id") Long id) {
//模擬學生數據
List<String> likes = Arrays.asList("抓魚", "爬山", "寫字");
StudentVO studentVO = new StudentVO(id, "張三", 22, "惠州", 93.5, likes);
return new Result<StudentVO>(200, "成功", studentVO);
}
@Operation(
summary = "查詢全部學生數據",
description = "查詢學生信息,并返回響應結果信息",
responses = {
@ApiResponse(
responseCode = "200",
description = "響應成功",
content = @Content(
mediaType = "application/json",
schema = @Schema(
title = "AjaxResul和StudentVO組合模型",
description = "返回實體,Result內data為StudentVO模型(并且StudentVO為集合)",
anyOf = {Result.class, StudentVO.class}
)
)
)
}
)
@GetMapping("/lists")
public Result<List<StudentVO>> findAllStudent() {
//模擬學生數據
List<String> likes = Arrays.asList("抓魚", "爬山", "寫字");
StudentVO student1 = new StudentVO(1L, "張三", 22, "深圳", 93.5, likes);
StudentVO student2 = new StudentVO(2L, "李四", 24, "惠州", 99.5, likes);
return new Result(200, "成功", Arrays.asList(student1, student2));
}
@Operation(summary = "學生查詢接口", description = "學生查詢接口")
@GetMapping("/query")
public Result<List<StudentVO>> queryStudent(QueryStudentDTO queryStudentDTO) {
//模擬學生數據
List<String> likes = Arrays.asList("抓魚", "爬山", "寫字");
StudentVO student1 = new StudentVO(1L, "張三", 22, "廣東深圳", 93.5, likes);
StudentVO student2 = new StudentVO(2L, "李四", 24, "廣東惠州", 99.5, likes);
return new Result<List<StudentVO>>(200, "成功", Arrays.asList(student1, student2));
}
@Operation(summary = "學生添加接口", description = "學生添加接口")
@PostMapping
public Result saveStudent(@RequestBody StudentDTO studentDTO) {
System.out.println("成功添加數據:" + studentDTO);
return new Result(200, "成功", null);
}
}