java脑洞 效率工程利器-代码解析工具 lp-base-export

脑洞的由来

开发过程中经常遇到

  1. 将Controller导出成API文档
  2. 将枚举注释导出,用于数据库注释或者API文档注释
  3. 将持久化的DO的注释导出,用于数据库注释
  4. 将错误码导出,用于API文档注释

当前常见解决方案

  1. ctrl + C 和 ctrl + V 大法,武林无敌之技
  2. swagger
  3. apiggs

效率对比

  1. 只要不怕累死,没啥缺点,主要是耗时
  2. 只能针对Controller生成文档,对代码有入侵,而且需要额外开启服务端口,产线禁止
  3. 同样针对Controller生产文档,和swagger相比,apiggs是基于代码解析生成的静态文档,对代码无入侵,而且不需要开启web服务,更安全更简便

介绍 lp-base-export

  1. 支持源码java文件解析,能拿到注释
  2. 支持字节码class文件解析,字节码解析是拿不到注释的
  3. 生成静态文档,无需开启web服务
  4. 对项目无入侵,在项目外通过maven插件或者ant等工具启动任务
  5. 生成文档样式全自定义,通过mvel表达式来解析生成
  6. 能递归获取父类属性
  7. 突破代码解析的最后壁垒(泛型),支持泛型解析后的泛型注入
  8. 能配合lombok(字节码解析支持,源码解析不支持)

版本

日期 版本号
2024-08-07 1.3.0.FINAL
2024-08-07 1.3.0-SPRING3.FINAL(针对spring3和jdk17的版本)

开始使用

方式一. 通过maven插件使用

1) 引入maven插件

<plugin>
                <groupId>io.github.wqr503</groupId>
                <artifactId>enum-export-plugin</artifactId>
                <version>1.3.0.FINAL</version>
                <configuration>
                    <taskList>
                        <task>
                            <id>enumTask</id>
                            <outPutDirection>${project.basedir}/export</outPutDirection>
                            <!--                            <classPaths>-->
                            <!--                                <classPath>${project.basedir}/target/classes</classPath>-->
                            <!--                            </classPaths>-->
                            <sourcePath>${project.basedir}/src/main/java</sourcePath>
                            <dependencyPaths>
                                <dependencyPath>${project.basedir}/target/lib</dependencyPath>
                            </dependencyPaths>
                            <logLevel>DEBUG</logLevel>
                            <logParam>true</logParam>
                            <mvlText>
                                <![CDATA[
public interface CombinationEnum {
@foreach{entity : entityList}
    // @{entity.typeName}
    String @{entity.name} = "@if{entity.desc != null}@{entity.desc} : @end{}@foreach{data : entity.valueList}@if{data.fieldList.size() > 0}@{data.fieldList[0].value}@end{}@if{data.fieldList.size() <= 0}@{data.ordinal}@end{}:@{data.name}(@if{data.desc != null}@{data.desc}@end{}),@end{}";
@end{}
}
  ]]>
                            </mvlText>
                        </task>
                    </taskList>
                </configuration>
            </plugin>

详情可看另一篇文章:
java脑洞 效率工程利器-代码解析maven插件 enum-export-plugin

方式二. 通过独立项目使用

1) jdk 要求 8+

2) 引入maven

<dependency>
  <groupId>io.github.wqr503</groupId>
  <artifactId>lp-base-export</artifactId>
  <version>1.3.0.FINAL</version>
</dependency>

3) 编写demo

1. 聚合输出,是指所有扫描出来的类聚合输出到一个文件里面,也就是所有扫描出来的类共用一个TableAttribute,TableAttribute中的getAttribute只会获取一次
new Exportor()
                // 输出地址
                .setBaseDir("D:\\lp-base-export\\export")
                // 源码路径地址(和字节码路径地址 二选一)
                //.setSourceJavaPath("D:\\lp-base-export\\src\\main\\java")
                // 字节码路径地址(源码路径地址 二选一)
                .setSourceClassPath("D:\\lp-base-export\\target\\classes")
                // 依赖包路径(可为空,没有依赖包则由于找不到Class则减少了扫描深度)
                .setDependencyPath("D:\\lp-base-export\\target\\lib")
                // 扫描的包路径(可为空)
                .setBasePackage("com.cn.lp.export")
                // 聚合输出的mvel表达式(和CombinationMvlPath 二选一)
//                .setCombinationMvlText(new CombinationMvlTexter() {
//                    @Override
//                    public String getCombinationMvelText() {
//                        return "测试输出:" +
//                                "//@{dto}\n";
//                    }
//                })
                // 聚合输出的mvel文件(和CombinationMvlText 二选一)
                .setCombinationMvlPath(new CombinationPather() {
                    @Override
                    public String getCombinationPath() {
                        return "D:\\lp-base-export\\export\\mvl\\dto.mvl";
                    }
                })
                // 具体输出文件名
                .setOutputCombinationFileName(new CombinationPather() {
                    @Override
                    public String getCombinationPath() {
                        return "EnumConstants.txt";
                    }
                })
                // 编程语言(语法糖解析)
                .setFormatter(LangFormatter.JAVA)
                // 扫描过滤器
                .addFilter(ClassFilterHelper.ofInclude(new Predicate<ScanClassInfo>() {
                    @Override
                    public boolean test(ScanClassInfo scanClassInfo) {
                        return true;
                    }
                }))
                // 构建提供给mvel的属性对象
                .setCreator(new TableAttributeCreator() {
                    @Override
                    public TableAttribute create() {
                        return new TableAttribute() {

                            private List<String> nameList = new ArrayList<>();

                            // 从扫描出来的对象中提取属性
                            @Override
                            public void putAttribute(ScanClassInfo classInfo, TypeFormatter typeFormatter) {
                                nameList.add(classInfo.getClassName());
                            }

                            // 输出给mvel的属性对象
                            @Override
                            public Map<String, Object> getAttribute() {
                                Map<String, Object> map = new HashMap<>();
                                map.put("nameList", nameList);
                                return map;
                            }
                        };
                    }
                })
                // 是否打印参数
                .setLogParam(true)
                // 设置打印等级 ERROR,WARN,INFO,DEBUG,TRACE
                .setLogLevel(Level.INFO)
                .combinationExportAll();

效果如下图:


image.png

image.png
2. 流水输出,是指所有扫描出来的类每个都会新建一个新的TableAttribute,根据TableAttribute中的getAttribute也会生成一个对应的文件,也就是扫描出3个类,就会有3个TableAttribute和3个对应生成的文件
new Exportor()
                // 输出地址
                .setBaseDir("D:\\lp-base-export\\export")
                // 源码路径地址(和字节码路径地址 二选一)
                //.setSourceJavaPath("D:\\lp-base-export\\src\\main\\java")
                // 字节码路径地址(源码路径地址 二选一)
                .setSourceClassPath("D:\\lp-base-export\\target\\classes")
                // 依赖包路径(可为空,没有依赖包则由于找不到Class则减少了扫描深度)
                .setDependencyPath("D:\\lp-base-export\\target\\lib")
                // 扫描的包路径(可为空)
                .setBasePackage("com.cn.lp.export")
                // 流水输出的mvel表达式(和MvlPath 二选一),可以不同对象对应不同mvel表达式
                .setMvlTexter(new MvlTexter() {
                    @Override
                    public String getMvelText(ScanClassInfo classInfo) {
                        return "测试输出:\n" +
                                "    @{name}\n";
                    }
                })
                // 流水输出的mvel文件(和MvlTexter 二选一),可以不同对象对应不同mvel文件
//                .setMvlPath(new Pather() {
//                    @Override
//                    public String getPath(ScanClassInfo classInfo) {
//                        return "D:\\lp-base-export\\export\\mvl\\dto.mvl";
//                    }
//                })
                // 流水输出文件名
                .setOutputFileName(new Pather() {
                    @Override
                    public String getPath(ScanClassInfo classInfo) {
                        return classInfo.getClassName().replace(".", "/") + "_Export.txt";
                    }
                })
                // 编程语言(语法糖解析)
                .setFormatter(LangFormatter.JAVA)
                // 扫描过滤器
                .addFilter(ClassFilterHelper.ofInclude(new Predicate<ScanClassInfo>() {
                    @Override
                    public boolean test(ScanClassInfo scanClassInfo) {
                        return true;
                    }
                }))
                // 构建提供给mvel的属性对象
                .setCreator(new TableAttributeCreator() {
                    @Override
                    public TableAttribute create() {
                        return new TableAttribute() {

                            private String name;

                            // 从扫描出来的对象中提取属性
                            @Override
                            public void putAttribute(ScanClassInfo classInfo, TypeFormatter typeFormatter) {
                                name = classInfo.getClassName();
                            }

                            // 输出给mvel的属性对象
                            @Override
                            public Map<String, Object> getAttribute() {
                                Map<String, Object> map = new HashMap<>();
                                map.put("name", name);
                                return map;
                            }
                        };
                    }
                })
                // 是否打印参数
                .setLogParam(true)
                // 设置打印等级 ERROR,WARN,INFO,DEBUG,TRACE
                .setLogLevel(Level.INFO)
                .exportAll();

效果如下图:


image.png

image.png

3. 对象描述

ScanClassInfo 字段描述

字段 类型 描述
className String 类名(包含包路径)
simpleName String 文件名
sourceClass SourceClass class对象

SourceClass 接口描述

字段 类型 描述
findAnnotation 查找注解 Optional<SourceAnnotation>
getAnnotationFieldMap 获取注解字段 - 只有注解类才有值 Map<String, SourceAnnotationField>
getMethodList 获取方法列表 List<SourceMethod>
getClassLoader 获取当前加载的ClassLoader ClassLoader
getClassType 获取类信息 SourceType
filterSuperClass 递归到最深查找是否有该父类 Collection<SourceType>
filterInterface 递归到最深查找是否有该接口 Collection<SourceType>
getEnumValueList 获取枚举值 List<SourceEnumValue>
getName 获取类名 String
getDesc 获取描述 String
getFieldMap 获取字段列表 ap<String, SourceField>
getAnnotationList 获取注解列表 List<SourceAnnotation>
SourceType接口描述
字段 类型 描述
findAnnotation 查找注解 Optional<SourceAnnotation>
getInterfaceList 获取接口列表 Collection<SourceType>
getSuperClass 获取父类 SourceType
isArray 是否数组 [] boolean
isEnum 是否枚举 boolean
isFinal 是否不可变 boolean
isAbstract 是否抽象 boolean
isAnnotationType 是否注解 boolean
isInterface 是否接口 boolean
isHasParameterizedType 是否泛型 boolean
getClassLoader 获取ClassLoader ClassLoader
getActualTypeArgumentMap 获取泛型Map Map<String, SourceType>
getTypeName 获取类路径(包含包路径) String
getSimpleName 获取类名 String
getFullName 获取包含泛型的名字 String

SourceAnnotation接口描述

字段 类型 描述
getName 获取名字 String
getFieldMap 获取字段列表 String
getClassLoader 获取ClassLoader ClassLoader
getSourceType 获取类信息 SourceType
getTypeName 获取类路径 String

SourceAnnotationField接口描述

字段 类型 描述
getReturnType 获取返回值 SourceType
getValue 获取值 Object
getName 获取名字 String
getDefaultValue 获取默认值 Object
getDesc 获取描述 String

SourceField接口描述

字段 类型 描述
getModifier 获取修饰符 String
isVolatile 是否volatile修饰 boolean
isStatic 是否static修饰 boolean
isFinal 是否final修饰 boolean
findAnnotation 查找某个注解 Optional<SourceAnnotation>
getAnnotationList 获取注解列表 List<SourceAnnotation>
getFieldType 获取字段类型 SourceType
getDesc 获取描述 String
getName 获取字段名 String

SourceEnumValue接口描述

字段 类型 描述
getOrdinal 获取原序号 Integer
getName 获取枚举名 String
getFieldList 获取值列表 List<SourceEnumValueField>
getDesc 获取描述 String

SourceEnumValueField接口描述

字段 类型 描述
getName 获取名字 String
getValue 获取值 Object

SourceParam接口描述

字段 类型 描述
getAnnotationList 获取注解列表 List<SourceAnnotation>
getParamType 获取类信息 SourceType
getName 获取名字 获取入参名

4. 拓展使用

  1. 配合Ant或者Gradle外部启动lp-base-export程序,分析当前项目代码再生成报告
  2. 封装成maven插件,通过maven插件调用,比如apiggs和enum-export-plugin

结语

起初该项目用于提取代码中枚举的注释然后批量生成接口注释,也可用于提取持久化对象字段的注释批量生成Mysql的字段的注释。最早是用Doclet来解析源码的,然后用ant来启动项目,其实这是相当不方便的,然后最近通过Javaparser来替代Doclet解析源码,减少了项目对jdk的依赖,同时通过提前暴露的思路攻克了泛型注入的问题,使得最后生成的对象更完整和更准确,同时也支持了内部类的解析。项目是独立项目,所以对分析的项目是零入侵,通过maven,gradle,ant等工具拉起项目,最后生成报告到指定目录。该项目我自己一直在使用,如果遇到问题可留言,我会单独联系给你解决。

如果这篇文章对你有帮助请给个star


image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容