在springboot3中,我们一般使用knife4j来集成springdoc,进而集成swagger-ui。
可swagger-ui需要在我们的类上增加很多注解,而我们通常开发是使用javadoc来完成注释的。
幸运的是,springdoc也可以解析javadoc自动生成相关内容,不再需要增加额外的注解。
该功能可以解析model类上面的注释,字段的注释,controller类上面的注释,方法上的注释,包括方法javadoc里面的 @param和 @return 都可以解析。

下面是具体是使用方案.

使用方案

首先引入knife4j,为了集成javadoc,还要引入therapi-runtime-javadoc,注意 maven-compiler-plugin也要做修改,pom参考:

<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.3.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>springboot-3</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-3</name>
    <description>springboot-3</description>
    <properties>
        <java.version>17</java.version>
        <knife4j.version>4.5.0</knife4j.version>
        <therapi-runtime-javadoc.version>0.15.0</therapi-runtime-javadoc.version>

    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.27</version>
        </dependency>
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
            <version>${knife4j.version}</version>
        </dependency>

        <dependency>
            <groupId>com.github.therapi</groupId>
            <artifactId>therapi-runtime-javadoc</artifactId>
            <version>${therapi-runtime-javadoc.version}</version>
        </dependency>
        
    </dependencies>

    <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>${therapi-runtime-javadoc.version}</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

然后,我们在application.yaml里面启用knife4j:

knife4j:
  enable: true

编写一个TestController,看看生成的api文档是否可以解析javadoc:

/**
 * 测试接口集合
 */
@RestController
public class TestController {

    /**
     * 测试接口
     * @param testParam 测试参数
     * @return 测试返回值
     */
    @GetMapping("/test")
    public String test(String testParam) {
        return testParam;
    }
}

访问 http://localhost:8080/doc.html :

在这里插入图片描述

可以看到,我们写的javadoc已经被解析成功。

但是,美中不足的是,我们在controller类上面写的注释,没有被识别成组,如图箭头所示。这个是因为springdoc解析controller上面的注释时,把他映射成@Tag的description属性,而name则使用类名生成。而knife4j显示的是name字段,这个可以通过访问 http://localhost:8080/swagger-ui.html 验证:
在这里插入图片描述

那么,我们有没有什么办法把它的description字段写到name上,让knife4j显示的更加友好呢?

增强方案

springdoc提供了修改器,可以修改生成后的api,我们自定义一个修改器,发现name是snake格式,并且description字段有值的Tag,就把他的description改到name字段上,参考代码如下:

@Component
@Slf4j
public class TagJavadocOpenApiCustomizer implements GlobalOpenApiCustomizer {
    private static final Logger log = LoggerFactory.getLogger(TagJavadocOpenApiCustomizer.class);
    private Predicate<String> snakeNameMatcher = Pattern.compile("^[a-zA-Z0-9-]+$").asPredicate();

    @Override
    public void customise(OpenAPI openApi) {
        log.info("TagJavadocOpenApi OpenApiCustomizer");
        // 把 controller上面的注释 生成TAG
        // 官方默认是把注释弄成了description,但是我们希望他的变成tag

        if (openApi.getTags() == null) {
            return;
        }

        List<Tag> tags = openApi.getTags();
        for (Tag tag : tags) {
            if (snakeNameMatcher.test(tag.getName()) && StringUtils.isNotEmpty(tag.getDescription())) {
                String newName = tag.getDescription();

                // 同步修改映射关系
                openApi.getPaths().forEach((s, pathItem) -> {
                    List<Operation> operations = new ArrayList<>();
                    addIfNotNull(operations, pathItem.getGet());
                    addIfNotNull(operations, pathItem.getDelete());
                    addIfNotNull(operations, pathItem.getDelete());
                    addIfNotNull(operations, pathItem.getOptions());
                    addIfNotNull(operations, pathItem.getPatch());
                    addIfNotNull(operations, pathItem.getPost());
                    addIfNotNull(operations, pathItem.getPut());
                    addIfNotNull(operations, pathItem.getTrace());

                    for (Operation operation : operations) {
                        for (int i = 0; i < operation.getTags().size(); i++) {
                            if (operation.getTags().get(i).equals(tag.getName())) {
                                operation.getTags().set(i, newName);
                            }
                        }
                    }

                });
                tag.name(newName);
            }
        }
    }

    private <T> void addIfNotNull(List<T> original, T itemsToAdd) {
        if (itemsToAdd != null) {
            original.add(itemsToAdd);
        }
    }
}

自定义的Customizer,只要注册成spring的bean,就会被自动应用。修改后结果如下:
在这里插入图片描述
可以看到,我们的注释都已经被成功解析了。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐