Swagger为什么不使用注释做接口描述

背景

使用Swagger的时候有一种痛苦,侵入性太强了。我个人又喜欢写注释,我理解注释写得越好,越能减少沟通的交流,节省人力,提高工作效率。所以想着使用Controller的注释和实体的注释,就能替换Swagger的***注解***。全网找下来,有实现这个功能的:github,但是我使用之后发现一些bug联系不上作者,而且我不会Kotlin, 所以还是需要自己研究一下。

设计

pom文件依赖

因为考虑想开源给大家使用,这里没有去依赖顶层的pom文件。

研究swagger的源码

通过研究swagger的源码发现,发现swagger是使用Plugin方式注册bean,使用DocumentationPluginsManager 来管理plugins。
允许一种类型的插件多种实现方式,这样就很方便了。

我们只需要拓展plugin就行。

我的目标是替换@Api@ApiOperation@ApiModel@ApiModelProperties,我基本上用到的就只有这些。

寻找读取注释的方法

运行时获取注释

这个是我最想要的方法,也确实有这种方法,可以参考下面的代码:

public class JavadocReader {
    /**
     * 为了方便gc
     */
    private static WeakReference<RootDoc> rootRef;
    /**
     * 操作cache需要获取synchronized锁,线程安全
     */
    private static HashMap<String, SwaggerJavadoc> cache = new HashMap<>();

    public static synchronized SwaggerJavadoc readJavaDoc(String fileFullPath, String clsName, boolean isField) {
        SwaggerJavadoc result = null;
        if (cache.get(clsName) != null) {
            return cache.get(clsName);
        }

        executeJavaDoc(fileFullPath);
        RootDoc root = rootRef.get();
        assert root != null;
        ClassDoc[] classes = root.classes();
        for (ClassDoc cls : classes) {

            //获取注解 class的注解
            result = new SwaggerJavadoc();
            result.setClassComment(cls.commentText());
            //获取filed的注解
            if (isField) {
                if (result.getFiledCommentMap() == null) {
                    result.setFiledCommentMap(new HashMap<>());
                }
                result.getFiledCommentMap().putAll(Arrays.stream(cls.fields(false)).collect(Collectors.toMap(Doc::name, FieldDoc::commentText)));
            } else { //获取方法的注解
                if (result.getMethodCommentMap() == null) {
                    result.setMethodCommentMap(new HashMap<>());
                }
                result.getMethodCommentMap().putAll(Arrays.stream(cls.methods()).collect(
                        Collectors.toMap(methodDoc -> String.format("%s_%s", methodDoc.name(), Arrays.toString(methodDoc.parameters())),
                                MethodDoc::commentText)));
            }

            cache.put(cls.qualifiedTypeName(), result);
        }
        rootRef.clear(); //help gc
        rootRef = null;
        return cache.get(clsName);
    }


    public static void executeJavaDoc(String targetPath, String fileFullPath) {
        // 参考了 https://blog.csdn.net/baiihcy/article/details/53861267
        com.sun.tools.javadoc.Main.execute(new String[] {"-doclet", SwaggerDoclet.class.getName(),
                // 因为自定义的Doclet类并不在外部jar中,就在当前类中,所以这里不需要指定-docletpath 参数,
                //"-docletpath",
                //Doclet.class.getResource("/").getPath(),
                "-encoding", "utf-8", "-classpath", targetPath,
                // 获取单个代码文件FaceLogDefinition.java的javadoc
                fileFullPath});
    }

    public static void executeJavaDoc(String fileFullPath) {
        // 参考了 https://blog.csdn.net/baiihcy/article/details/53861267
        com.sun.tools.javadoc.Main.execute(new String[] {"-doclet", SwaggerDoclet.class.getName(),
                // 因为自定义的Doclet类并不在外部jar中,就在当前类中,所以这里不需要指定-docletpath 参数,
                //"-docletpath",
                //Doclet.class.getResource("/").getPath(),
                "-encoding", String.valueOf(StandardCharsets.UTF_8),
                "-classpath",
                ".",
                fileFullPath});
    }

    /**
     * 用来映射注释适合Swagger<br/>
     * date 2020/11/6 <br/>
     * email yuan.donghao@qq.com
     *
     * @author 袁小黑
     * @version 1.0.0
     **/

    public static class SwaggerDoclet extends Doclet {

        public static boolean start(RootDoc root) {
            JavadocReader.rootRef = new WeakReference<>(root);
            return true;
        }

    }
}

 测试文件
public static final String RequestFullPath = JavadocReaderTest.class.getResource("/").getPath()+"../../src/test/java/self/donghao/swagger/extension/dto/Request.java";
    public static final String MethodFullPath = JavadocReaderTest.class.getResource("/").getPath()+"../../src/test/java/self/donghao/swagger/extension/method/DubboSpringCloudClientBootstrap.java";

    public static void main(String[] args) {
        System.out.println(JavadocReaderTest.class.getResource("/").getPath());
    }

    @Test
    public void readJavaDocTestRequest() {
        System.out.println(JavadocReader.readJavaDoc(RequestFullPath, Request.class.getName(), true));
        System.out.println(JavadocReader.readJavaDoc(RequestFullPath, Request.InnerRequest.class.getName().replace("$", "."), true));
    }
	...

上面的代码是我编写的参考网上的资料编写的,可以运行时获取java注释,也经过了本人实验。
但是有个很大的问题:Java编译器编译Java文件之后会去除这些注释。
上面的代码也是从Java文件中动态读取注释的。这时我也开始理解SpringFox团队为什么不提供注释的plugin而是使用侵入性比较强的注解了。注解的获取非常简单,使用反射即可,注释的获取非常难,这不仅仅是我们上面提到的问题,还有问题就是jdk8、jdk9~jdk14之间的javadoc运行的Main方法不是在同一个package下,这样就更加困难了。我不得不参考springfox-plus,将这个运行时获取注释拆分成两步:

1)生成注释

2)读取注释并将注释内容放入Swagger的Plugin

生成注释

生成注释主要思路是在maven的compile阶段绑定一个maven-plugin(maven的生命周期),运行javadoc生成类描述json文件,并且写到META-INF下。

具体感兴趣的小伙伴可以看这里:javadoc-maven-plugin

读取注解并更改Swagger的Plugin

其实就是读取META-INF下的文件,更新到Swagger的Plugin。

写和读文件都是使用Jackson的ObjectMappler,Swagger的每个注解都有对应的Plugin,读者感兴趣可以去研究一下。这里给出我使用到的Plugin:ApiListingBuilderPlugin, ModelBuilderPluginOperationBuilderPluginModelPropertyBuilderPlugin

@Component
@Order(AFTER_SWAGGER)
public class CustomApiBuilder implements ApiListingBuilderPlugin {
    @Override
    public void apply(ApiListingContext apiListingContext) {
        ClassDescription classDescription = ClasspathDocStore
            .read(
            apiListingContext
            .getResourceGroup()
            .getControllerClass()
            .get()
            .getName()); //读取META-INF下的文件,反序列化成ClassDescription
        if (classDescription == null || !StringUtils.hasText(classDescription.getDescription())) {
            return;
        }
        //设置
        apiListingContext
            .apiListingBuilder()
            .description( classDescription.getDescription());
    }

    @Override
    public boolean supports(DocumentationType documentationType) {
        return true;
    }
}

上面是一个替换Swagger的@Api的例子,内容比较简单。

更加具体的内容可以看这里:Swagger-Javadoc

使用

要求jdk 1.8

  1. 在pom文件中绑定complie阶段。
<build>
    <plugins>
        <plugin>
            <groupId>self.donghao.extension.maven</groupId>
            <artifactId>self-swagger-extension-maven</artifactId>
            <version>1.0-SNAPSHOT</version>
            <executions>
                <execution>
                    <id>javadoc</id>
                    <phase>compile</phase>
                    <goals>
                        <goal>javadoc</goal>
                    </goals>
                    <configuration>
                        <packages>
                            <p>self.donghao.demos.swagger.ctrl</p>
                            <p>self.donghao.demos.swagger.dto</p> <!--扫描的包-->
                        </packages>
                    </configuration>
                </execution>
            </executions>
        </plugin>
        ...
  1. 运行maven compile(注意这个时候使用IDEA直接运行spring-boot是没办法获取注释的)

  2. 运行项目。

    最后看看效果图
    在这里插入图片描述

    其实跳出项目来看,这种实现方式和原生的Swagger是有取有舍,这种消耗了 部分存储空间和内存来获取注释。如果从注释角度,这种消耗肯定更加小的,看大众的接受哪种方式吧。究竟哪种方式更好,这个是没有定论的。

    这部分开源的代码没有做一些缓存优化(笔者没有时间)。如果同学用的话,这个坑需要注意一下。

参考项目

1. springfox-plus

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值