java获取注释内容_Swagger为什么不使用注释做接口描述

本文介绍了如何在Java中获取注释内容,并探讨了Swagger为何不直接使用注释作为接口描述。通过研究Swagger源码,找到读取注释的方法,并在运行时获取和生成注释。此外,还展示了如何读取注解并修改Swagger的Plugin,最终实现了自定义的接口描述。注意,开源代码中可能存在未进行缓存优化的问题。
摘要由CSDN通过智能技术生成

ff23576e1c52bbacfe6b62c9967ebf89.png

背景

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

设计

研究swagger的源码

通过研究swagger的源码发现,发现swagger是使用Plugin方式注册bean,使用 DocumentationPluginsManager 来管理plugins。允许一种类型的插件多种实现方式,这样就很方便了。 我们只需要拓展plugin就行。 我的目标是替换 @Api 、 @ApiOperation 、 @ApiModel 、 @ApiModelProperties ,我基本上用到的就只有这些。

寻找读取注释的方法

运行时获取注释

这个是我最想要的方法,也确实有这种方法,可以参考下面的代码:
public class JavadocReader {    /**     * 为了方便gc     */    private static WeakReferencerootRef;    /**     * 操作cache需要获取synchronized锁,线程安全     */    private static HashMap<< span="">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     * date 2020/11/6      * 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 , ModelBuilderPlugin 、 OperationBuilderPlugin 、 ModelPropertyBuilderPlugin 。
@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.mavengroupId>            <artifactId>self-swagger-extension-mavenartifactId>            <version>1.0-SNAPSHOTversion>            <executions>                <execution>                    <id>javadocid>                    <phase>compilephase>                    <goals>                        <goal>javadocgoal>                    goals>                    <configuration>                        <packages>                            <p>self.donghao.demos.swagger.ctrlp>                            <p>self.donghao.demos.swagger.dtop>                         packages>                    configuration>                execution>            executions>        plugin>        ...
2 . 运行maven compile(注意这个时候使用IDEA直接运行spring-boot是没办法获取注释的) 3. 运行项目。

 最后看看效果图904233165c9852d3f73865594233fdf3.png

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

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

参考项目

1. springfox-plus

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值