Java 17 Spring Boot 3.5.5 Springdoc OpenAPI 2.8.13

Maven 依赖

xml
1
2
3
4
5
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    <version>2.8.13</version>
</dependency>

请注意与 Spring Boot 版本的兼容,兼容矩阵可查看 FAQ

相关配置

OpenAPI doc

详细配置项可参考 官方文档

默认路径:

text
1
http://localhost:8080/v3/api-docs

我们可以通过配置 springdoc.api-docs.path 来自定义 OpenAPI doc 的路径。

例如:

properties
1
2
# 当我们如下配置时,OpenAPI doc 的路径为: http://localhost:808/api-docs
springdoc.api-docs.path=/api-docs

默认情况下,OpenAPI doc 为 JSON 格式,我们可以通过如下配置修改为 yaml:

properties
1
springdoc.api-docs.path=/api-docs.yaml

Swagger UI

详细配置项可参考 官方文档

默认路径:

text
1
http://localhost:8080/swagger-ui/index.html

我们可以通过配置 springdoc.swagger-ui.path 来自定义 Swagger UI 的路径。

例如:

properties
1
2
# 当我们如下配置时,Swagger UI 的路径为: http://localhost:8080/swagger-ui-custom.html
springdoc.swagger-ui.path=/swagger-ui-custom.html

使用示例

假设我们有一个 BookController 类,用于处理 Book 相关的请求,内容如下:

java
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package cn.tofuwine.swaggerui.controller; 

import java.util.Collection;

import cn.tofuwine.swaggerui.exception.BookNotFoundException;
import cn.tofuwine.swaggerui.model.Book;
import cn.tofuwine.swaggerui.repository.BookRepository;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

/**
 * The type Book controller.
 */
@RestController
@RequestMapping("/api/book")
public class BookController {

    /**
     * The Repository.
     */
    private final BookRepository repository;

    /**
     * Instantiates a new Book controller.
     *
     * @param repository the repository
     */
    public BookController(BookRepository repository) {
        this.repository = repository;
    }

    /**
     * Find by id book.
     *
     * @param id the id
     * @return the book
     */
    @GetMapping("/{id}")
    public Book findById(@PathVariable long id) {
        return repository.findById(id)
                .orElseThrow(BookNotFoundException::new);
    }

    /**
     * Find books collection.
     *
     * @return the collection
     */
    @GetMapping("/")
    public Collection<Book> findBooks() {
        return repository.getBooks();
    }

    /**
     * Update book.
     *
     * @param id   the id
     * @param book the book
     * @return the book
     */
    @PutMapping("/{id}")
    @ResponseStatus(HttpStatus.OK)
    public Book updateBook(@PathVariable("id") final String id, @RequestBody final Book book) {
        return book;
    }

    /**
     * Patch book.
     *
     * @param id   the id
     * @param book the book
     * @return the book
     */
    @PatchMapping("/{id}")
    @ResponseStatus(HttpStatus.OK)
    public Book patchBook(@PathVariable("id") final String id, @RequestBody final Book book) {
        return book;
    }

    /**
     * Post book.
     *
     * @param book the book
     * @return the book
     */
    @PostMapping("/")
    @ResponseStatus(HttpStatus.CREATED)
    public Book postBook(@NotNull @Valid @RequestBody final Book book) {
        return book;
    }

    /**
     * Head book.
     *
     * @return the book
     */
    @RequestMapping(method = RequestMethod.HEAD, value = "/")
    @ResponseStatus(HttpStatus.OK)
    public Book headBook() {
        return new Book();
    }

    /**
     * Delete book long.
     *
     * @param id the id
     * @return the long
     */
    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.OK)
    public long deleteBook(@PathVariable final long id) {
        return id;
    }
}

那么当我们运行程序时无需任何配置,即可生成对应的 OpenAPI 文档和 Swagger UI。

打开浏览器,输入网址:http://localhost:8080/v3/api-docs 可以看到 OpenAPI 文档(注:JSON 格式,由于我浏览器使用了 JSON Viewer Pro 插件,界面显示稍有不同)

openapi-docs

紧接着,我们打开 http://localhost:8080/swagger-ui/index.html 可以看到 Swagger UI 界面:

swagger-ui

很显然,因为我们未配置任何内容,所以生成的文档很多内容都是空的或默认值。我们需要根据实际情况进行修改。

首先,我们可以定义一个 OpenAPIConfig 类,用于配置 OpenAPI 文档的相关信息(如标题、版本、描述等)。

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import io.swagger.v3.oas.models.Components;
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.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class OpenAPIConfig {

    @Bean
    public OpenAPI customOpenAPI(@Value("${app.version}") String version) {
        return new OpenAPI()
                .components(new Components())
                .info(new Info()
                        .title("Book API")
                        .version(version)
                        .description("API documentation for Book endpoints")
                        .license(new License().name("Apache 2.0").url("http://springdoc.org")));
    }
}

这样,文档的标题就变成了这样:

image-20250917165426954

对于接口方法的描述等信息变更,可通过传统的添加注解的方式,如 @Operation@ApiResponse 等注解进行配置,或更为便捷的 javadoc 方式。

Swagger 注解式写法

以下仅列举常用注解:

注解作用
@Tag用于定义 API 分组的标签,每个 API 操作可以有多个标签。
@Operation用于定义 API 操作的详细信息,包括操作的描述、请求方法、参数、响应等。
@ApiResponse用于定义 API 操作的响应的详细信息,包括响应的状态码、响应的描述、响应的示例值等。

现在我们以 BookController 为例,添加 Swagger UI 注解:

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@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(BookNotFoundException::new);
}

Javadoc 写法 [推荐]

我们可以看到对于注解形式的写法,代码侵入性强,大量注解充斥在 Controller 里面,实际业务代码占比很小。那么有没有更便捷的方式,代码修改更少的方式去实现呢?答案就是 javadoc。

Springdoc-openapi 支持 Javadoc 注释,我们可以在 Controller 类、方法、参数等上添加 Javadoc 注释,这些注释会被解析并生成对应的 API 文档。

但需要我们导入 therapi-runtime-javadoc 依赖:

xml
1
2
3
4
5
6
<!-- Runtime library -->
<dependency>
    <groupId>com.github.therapi</groupId>
    <artifactId>therapi-runtime-javadoc</artifactId>
    <version>0.15.0</version>
</dependency>
xml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<!--Annotation processor -->
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <annotationProcessorPaths>
                    <path>
                        <groupId>com.github.therapi</groupId>
                        <artifactId>therapi-runtime-javadoc-scribe</artifactId>
                        <version>0.15.0</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>

两种写法对比:

维度注解式写法Javadoc 式写法
代码侵入性高:需在类、方法、参数上添加大量注解低:仅需维护标准 Javadoc 注释,无额外代码
业务代码占比低:注解代码可能多于业务逻辑高:注释与业务代码分离,不干扰逻辑阅读
维护成本高:注解需与业务同步修改(如接口名、参数变了,注解也要改)低:修改业务时同步更新注释即可,无需额外改注解
学习成本需学习特定框架的注解规则(如 @Operation@ApiResponse 搭配)基本没有

FAQ

Spring Boot 与 Springdoc OpenAPI 版本兼容矩阵

更多内容请查看 官方文档

Spring Boot 版本Springdoc OpenAPI 版本
3.5.x2.8.x
3.4.x2.7.x - 2.8.x
3.3.x2.6.x
3.2.x2.3.x - 2.5.x
3.1.x2.2.x
3.0.x2.0.x - 2.1.x
2.7.x、1.5.x1.6.0+
2.6.x、1.5.x1.6.0+
2.5.x、1.5.x1.5.9+
2.4.x、1.5.x1.5.0+
2.3.x、1.5.x1.4.0+
2.2.x、1.5.x1.2.1+
2.0.x、1.5.x1.0.0+

Springdoc-openapi BOM

2.8.7 开始,Springdoc OpenAPI 提供了一个 BOM(Bill of Materials),用于管理依赖版本。

xml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-bom</artifactId>
            <version>2.8.13</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

异常处理

Springdoc-openapi 支持自定义异常处理的文档展示,我们可以通过 @ControllerAdvice@ResponseStatus 注解来实现。

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;


@RestControllerAdvice
public class GlobalControllerExceptionHandler {

    @ExceptionHandler(ConversionFailedException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ProblemDetail handleConversion(RuntimeException e) {
        ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, e.getMessage());
        problemDetail.setTitle("Bookmark is Not Found");
        problemDetail.setProperty("errorCategory", "Generic Exception");
        problemDetail.setProperty("timestamp", Instant.now());
        return problemDetail;
    }

    @ExceptionHandler(BookNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ProblemDetail handleBookNotFound(RuntimeException e) {
        ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, e.getMessage());
        problemDetail.setTitle("Book Not Found");
        problemDetail.setProperty("errorCategory", "Generic Exception");
        problemDetail.setProperty("timestamp", Instant.now());
        return problemDetail;
    }
}

分页参数

Spring Data JPA 与 Spring MVC 可以无缝集成,自 springdoc-openapi v1.6.0 版本起,就已开箱即用地支持 Pageable。 page、size 和 sort 查询参数会被添加到生成的文档中:

示例:

java
1
2
3
4
5
6
import org.springdoc.core.annotations.ParameterObject;

@GetMapping("/filter")
public Page<Book> filterBooks(@ParameterObject Pageable pageable) {
     return repository.getBooks(pageable);
}

参考链接