java 脑洞

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

2024-08-06  本文已影响0人  灰色调诺言

脑洞的由来

开发过程中经常遇到

  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
上一篇下一篇

猜你喜欢

热点阅读