➜ Old React website
Chung Cheuk Hang MichaelJava Web Developer
自建 Nexus Maven repoRabbitMQ 基礎

Spring Boot 項目使用 Swagger/OpenAPI

Table of contents

1 OpenAPI 簡介

OpenAPI Specification(OAS)係由 Swagger Specification 演變出黎既一套 HTTP API 格式規範,係開發者之間表達 HTTP APIs 既其中一個標準。
喺 2015,SmartBear 公司收購左開源既 Swagger Specification,然後喺 2015 年年尾,SmartBear 將 Swagger Specification 捐左畀 Linux Foundation 旗下既 OpenAPI Initiative 公司,亦將佢改名成 OpenAPI Specification,亦即係 Swagger 2.0 ➜ OpenAPI 3.0.0
至於 Swagger UI 就係一個工具,準確黎講係一個網頁既介面,畀我地好容易咁測試到我地寫既 HTTP endpoints。

1.1 API-first 開發方式

API-first 就係以 API 主導既開發方式,我地會先寫 API contract,亦即係 Swagger/OpenAPI Specification,然後再寫 code。

1.2 Cloud platforms 對 OpenAPI Specification 既支援

以下係熱門既 cloud platforms 既 API import 功能支援既 OpenAPI Specification 版本:
Cloud platforms支援既 OpenAPI Specification 版本
Amazon AWS API Gateway2.03.0
Microsoft Azure API Management2.03.03.1
Google Cloud API Gateway2.0
Alibaba Cloud API Gateway2.03.0
參考資料:

2 動手寫

我地會寫一個有齊曬模擬既 CRUD endpoints 既 Spring Boot MVC 項目。

2.1 Maven dependencies

1<?xml version="1.0" encoding="UTF-8"?> 2<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 3 <modelVersion>4.0.0</modelVersion> 4 5 <groupId>com.michael</groupId> 6 <artifactId>Spring-Boot-3-OpenAPI-Code-Gen-MVC-Demo</artifactId> 7 <version>1.0.0</version> 8 <packaging>jar</packaging> 9 10 <name>Spring-Boot-3-OpenAPI-Code-Gen-MVC-Demo</name> 11 <description>Spring-Boot-3-OpenAPI-Code-Gen-MVC-Demo</description> 12 13 <properties> 14 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> 15 <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> 16 <java.version>17</java.version> 17 <javac.source>17</javac.source> 18 <javac.target>17</javac.target> 19 <maven.compiler.target>17</maven.compiler.target> 20 <maven.compiler.source>17</maven.compiler.source> 21 22 <start-class>code.MainApplication</start-class> 23 </properties> 24 25 <parent> 26 <groupId>org.springframework.boot</groupId> 27 <artifactId>spring-boot-starter-parent</artifactId> 28 <version>3.2.5</version> 29 </parent> 30 31 <dependencies> 32 <dependency> 33 <groupId>org.springframework.boot</groupId> 34 <artifactId>spring-boot-starter-web</artifactId> 35 </dependency> 36 37 <!-- Swagger UI --> 38 <dependency> 39 <groupId>org.springdoc</groupId> 40 <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> 41 <version>2.5.0</version> 42 </dependency> 43 44 <!-- OpenAPI generator Maven plugin 會生成用到佢既 Java code --> 45 <dependency> 46 <groupId>org.openapitools</groupId> 47 <artifactId>jackson-databind-nullable</artifactId> 48 <version>0.2.6</version> 49 </dependency> 50 51 <dependency> 52 <groupId>net.logstash.logback</groupId> 53 <artifactId>logstash-logback-encoder</artifactId> 54 <version>7.4</version> 55 </dependency> 56 57 <dependency> 58 <groupId>org.apache.commons</groupId> 59 <artifactId>commons-lang3</artifactId> 60 </dependency> 61 62 <dependency> 63 <groupId>org.projectlombok</groupId> 64 <artifactId>lombok</artifactId> 65 <scope>provided</scope> 66 </dependency> 67 </dependencies> 68 69 <build> 70 <plugins> 71 <plugin> 72 <groupId>org.springframework.boot</groupId> 73 <artifactId>spring-boot-maven-plugin</artifactId> 74 <executions> 75 <execution> 76 <goals> 77 <goal>repackage</goal> 78 </goals> 79 </execution> 80 </executions> 81 </plugin> 82 83 <!-- OpenAPI generator Maven plugin --> 84 <plugin> 85 <groupId>org.openapitools</groupId> 86 <artifactId>openapi-generator-maven-plugin</artifactId> 87 <version>7.5.0</version> 88 <executions> 89 <execution> 90 <goals> 91 <goal>generate</goal> 92 </goals> 93 <configuration> 94 <inputSpec>${project.basedir}/src/main/resources/openapi.yml</inputSpec> 95 <generatorName>spring</generatorName> 96 97 <configOptions> 98 <apiPackage>code.api</apiPackage> 99 <modelPackage>code.model</modelPackage> 100 <useSpringBoot3>true</useSpringBoot3> 101 102 <!-- 我地可以用 delegatePattern --> 103 <delegatePattern>true</delegatePattern> 104 105 <!-- 或者用 interfaceOnly --> 106 <interfaceOnly>true</interfaceOnly> 107 </configOptions> 108 </configuration> 109 </execution> 110 </executions> 111 </plugin> 112 </plugins> 113 </build> 114</project>
註:
  • OpenAPI generator Maven plugin 提供兩種生成 Java model code 既方式。
    • delegatePattern
    • interfaceOnly

2.2 寫 Swagger/OpenAPI Specification

因為係 API-first 既開發方式,所以我地寫 Java code 之前要先寫 Swagger/OpenAPI Specification 檔案。
src/main/resources/openapi.yml
1openapi: 3.0.0 2info: 3 version: 1.0.0 4 title: Spring-Boot-3-OpenAPI-Code-Gen-MVC-Demo 5 description: OpenAPI for Spring-Boot-3-OpenAPI-Code-Gen-MVC-Demo 6paths: 7 /users: 8 get: 9 operationId: getUsers 10 summary: Get all users 11 description: Get all users. 12 responses: 13 '200': 14 description: OK 15 content: 16 application/json: 17 schema: 18 type: array 19 items: 20 $ref: '#/components/schemas/UserDto' 21 post: 22 operationId: createUser 23 summary: Create user by ID 24 description: Create a user of the given ID. 25 requestBody: 26 required: true 27 description: User object (JSON). 28 content: 29 application/json: 30 schema: 31 $ref: '#/components/schemas/UserDto' 32 responses: 33 '200': 34 description: OK 35 content: 36 application/json: 37 schema: 38 $ref: '#/components/schemas/UserDto' 39 /users/{userId}: 40 get: 41 operationId: getUser 42 summary: Get user by ID 43 description: Get a user of the given ID. 44 parameters: 45 - name: userId 46 in: path 47 required: true 48 description: User ID. 49 schema: 50 type : long 51 responses: 52 '200': 53 description: OK 54 content: 55 application/json: 56 schema: 57 $ref: '#/components/schemas/UserDto' 58 put: 59 operationId: updateUser 60 summary: Update user by ID 61 description: Update a user of the given ID. 62 parameters: 63 - name: userId 64 in: path 65 required: true 66 description: User ID. 67 schema: 68 type : long 69 requestBody: 70 required: true 71 description: User object (JSON). 72 content: 73 application/json: 74 schema: 75 $ref: '#/components/schemas/UserDto' 76 responses: 77 '200': 78 description: OK 79 content: 80 application/json: 81 schema: 82 $ref: '#/components/schemas/UserDto' 83 delete: 84 operationId: deleteUser 85 summary: Delete user by ID 86 description: Delete a user of the given ID. 87 parameters: 88 - name: userId 89 in: path 90 required: true 91 description: User ID. 92 schema: 93 type : long 94 responses: 95 '200': 96 description: OK 97 content: 98 application/json: 99 schema: 100 $ref: '#/components/schemas/UserDto' 101components: 102 schemas: 103 UserDto: 104 type: object 105 properties: 106 id: 107 type: long 108 firstName: 109 type: string 110 lastName: 111 type: string

2.3 寫 Java code

無論係 interfaceOnly 或者係 delegatePattern,都係用一樣既 main class。
MainApplication.java
1@SpringBootApplication 2public class MainApplication { 3 4 public static void main(String[] args) { 5 SpringApplication.run(MainApplication.class, args); 6 } 7}

2.3.1 interfaceOnly 版本

Project structure:
  • src/main/java
    • /code
      • MainApplication.java
      • UsersController.java
  • target/generated-sources/openapi/src/main/java(由 OpenAPI generator Maven plugin 生成)
    • /code
      • /api
        • ApiUtil.java
        • UsersApi.java
      • /model
        • UserDto.java
UsersController.java
1@Slf4j 2@RestController 3public class UsersController implements UsersApi { 4 5 @Override 6 public ResponseEntity<List<UserDto>> getUsers() { 7 log.info("getUsers"); 8 return ResponseEntity.ok(Arrays.asList(randomUser(), randomUser())); 9 } 10 11 @Override 12 public ResponseEntity<UserDto> getUser(Long userId) { 13 log.info("getUser"); 14 return ResponseEntity.ok(randomUser()); 15 } 16 17 @Override 18 public ResponseEntity<UserDto> createUser(UserDto user) { 19 log.info("createUser"); 20 return ResponseEntity.ok(user); 21 } 22 23 @Override 24 public ResponseEntity<UserDto> updateUser(Long userId, UserDto user) { 25 log.info("updateUser"); 26 return ResponseEntity.ok(user); 27 } 28 29 @Override 30 public ResponseEntity<UserDto> deleteUser(Long userId) { 31 log.info("deleteUser"); 32 return ResponseEntity.ok(randomUser()); 33 } 34 35 36 37 private UserDto randomUser() { 38 final UserDto user = new UserDto(); 39 user.setId(new Random().nextLong(100)); 40 user.setFirstName(RandomStringUtils.randomAlphabetic(10)); 41 user.setLastName(RandomStringUtils.randomAlphabetic(10)); 42 return user; 43 } 44}
註:
  • UsersApi 係由 OpenAPI generator Maven plugin 生成既 Java interface。
  • UserDto 係由 OpenAPI generator Maven plugin 生成既 Java class。

2.3.2 delegatePattern 版本

Project structure:
  • src/main/java
    • /code
      • MainApplication.java
      • UsersApiDelegateImpl.java
  • target/generated-sources/openapi/src/main/java(由 OpenAPI generator Maven plugin 生成)
    • /code
      • /api
        • ApiUtil.java
        • UsersApi.java
        • UsersApiController.java
        • UsersApiDelegate.java
      • /model
        • UserDto.java
    • /org(唔重要,因為我地唔會用裡面既野)
      • /openapitools
        • /configuration
          • HomeController.java
          • SpringDocConfiguration.java
        • OpenApiGeneratorApplication.java
        • RFC3339DateFormat.java
UsersApiDelegateImpl.java
1@Slf4j 2@Component 3public class UsersApiDelegateImpl implements UsersApiDelegate { 4 5 @Override 6 public ResponseEntity<List<UserDto>> getUsers() { 7 log.info("getUsers"); 8 return ResponseEntity.ok(Arrays.asList(randomUser(), randomUser())); 9 } 10 11 @Override 12 public ResponseEntity<UserDto> getUser(Long userId) { 13 log.info("getUser"); 14 return ResponseEntity.ok(randomUser()); 15 } 16 17 @Override 18 public ResponseEntity<UserDto> createUser(UserDto user) { 19 log.info("createUser"); 20 return ResponseEntity.ok(user); 21 } 22 23 @Override 24 public ResponseEntity<UserDto> updateUser(Long userId, UserDto user) { 25 log.info("updateUser"); 26 return ResponseEntity.ok(user); 27 } 28 29 @Override 30 public ResponseEntity<UserDto> deleteUser(Long userId) { 31 log.info("deleteUser"); 32 return ResponseEntity.ok(randomUser()); 33 } 34 35 36 37 private UserDto randomUser() { 38 final UserDto user = new UserDto(); 39 user.setId(new Random().nextLong(100)); 40 user.setFirstName(RandomStringUtils.randomAlphabetic(10)); 41 user.setLastName(RandomStringUtils.randomAlphabetic(10)); 42 return user; 43 } 44}
註:
  • UsersApiDelegate 係由 OpenAPI generator Maven plugin 生成既 Java interface。
  • UserDto 係由 OpenAPI generator Maven plugin 生成既 Java class。

2.4 Application 配置

如果想改變 Swagger UI 既 endpoint paths,可以覆蓋默認既配置:
springdoc: swagger-ui.path: "/api/swagger/swagger-ui.html" # 默認:/swagger-ui.html api-docs.path: "/api/swagger/v3/api-docs" # 默認:/v3/api-docs

3 測試

3.1 cURL

列出所有 users:
curl -X GET http://localhost:8080/users
列出一個 user:
curl -X GET http://localhost:8080/users/1234
新增一個 user:
curl -X POST http://localhost:8080/users -H "Content-Type: application/json" -d "{ \"id\": 123, \"firstName\": \"Michael\", \"lastName\": \"Chung\" }"
更新一個 user:
curl -X PUT http://localhost:8080/users/1234 -H "Content-Type: application/json" -d "{ \"id\": 123, \"firstName\": \"Michael\", \"lastName\": \"Chung\" }"
刪除一個 user:
curl -X DELETE http://localhost:8080/users/1234

3.2 Swagger UI

  1. 喺瀏覽器打開 http://localhost:8080/swagger-ui.html
  2. 佢會自動 redirect 去 http://localhost:8080/swagger-ui/index.html
  3. 打開其中一個 API。
  4. 撳「Test it out」。
  5. 輸入必填既 request 資訊(如有)。
    • Path variables
    • Request parameters
    • Request body
  6. 撳「Execute」。

3.3 取得 OpenAPI Specification 檔案

Swagger UI 亦會 expose 以下既 HTTP endpoints 畀我地拎到我地 microservice 既 OpenAPI Specification 檔案,我地只需要喺瀏覽器打開以下既 URLs:

4 筆記

4.1 OpenAPI generator Maven plugin 配置

configOptions 配置解釋
apiPackage喺邊個 Java package 去生成 Java API interfaces/classes。
modelPackage喺邊個 Java package 去生成 Java model classes。
useSpringBoot3用黎控制生成既項目以及 Java code 係咪 Spring Boot 3.x。如果係既話,會生成 jakarta 而唔係 javax package 既 import statements。
interfaceOnly會生成 Java API interface 畀我地 override 佢既所有 method body。
delegatePattern相比起 interfaceOnly,會生成額外既 Spring controller class(@Controller)以及叫 delegate 既 Java interface 畀我地 override 佢既所有 method body。

4.1.1 OpenAPI generator Maven plugin - interfaceOnly

  • 會生成 Java API interface。
    • 每個 method 都會有 @RequestMapping,return type 係 ResponseEntity
    • 我地要 override 佢既所有 method body,否則默認會 return HttpStatus.NOT_IMPLEMENTED

4.1.2 OpenAPI generator Maven plugin - delegatePattern

  • 會生成叫 delegate 既 Java interface。
    • 我地要 override 佢既所有 method body,否則默認會 return HttpStatus.NOT_IMPLEMENTED
  • 會生成 Java API interface。
    • 每個 method 都會有 @RequestMapping,return type 係 ResponseEntity,默認會 return delegate 既 method 既 result。
  • 會生成 Spring controller class。
    • Class level 會有:
      • @Controller
      • @RequestMapping("${openapi.xxx.base-path:}")xxx 黎自個 OpenAPI Specification 既 info.title field)。
    • 佢 implement 左個 Java API interface,所以 Spring Boot 就會 expose 黎自個 Java API interface 既 @RequestMapping 既 HTTP endpoints。
  • 會生成 main class 以及其他唔係太有用既 Java classes。

4.2 OpenAPI Specification fields

OpenAPI Specification field用途
openapiOpenAPI Specification 既版本號碼。
paths每個 expose 既 HTTP endpoint path 既配置。
HTTP endpoint path(e.g. /users裡面係佢支援既每個 HTTP request method 既配置。
operationId(e.g. getUsers會影響 reference 呢個 API operation 既 ID,對 OpenAPI generator Maven plugin 黎講會影響生成出黎既 Java method name。
schemaData type,可以係 OpenAPI 本身支援 basic data types,可以用 $ref 黎 reference components.schemas 裡面既自定義 data types,亦可以係 array(再用 items field 表示 array elements 既 data type)。
requestBodyHTTP request body 既配置。
parametersHTTP request body 以外既配置,例如 path variables(要用 in: path)、request parameters(亦叫 query strings,要用 in: query)、request headers(要用 in: header)。
responseHTTP response 每個支援既 HTTP status code 既配置。
components.schemas自定義既 data types,對 OpenAPI generator Maven plugin 黎講會影響生成出黎既 Java model classes。

5 參考資料