java脑洞 效率工程利器-代码解析工具 lp-base-exp
2024-08-06 本文已影响0人
灰色调诺言
脑洞的由来
开发过程中经常遇到
- 将Controller导出成API文档
- 将枚举注释导出,用于数据库注释或者API文档注释
- 将持久化的DO的注释导出,用于数据库注释
- 将错误码导出,用于API文档注释
当前常见解决方案
- ctrl + C 和 ctrl + V 大法,武林无敌之技
- swagger
- apiggs
效率对比
- 只要不怕累死,没啥缺点,主要是耗时
- 只能针对Controller生成文档,对代码有入侵,而且需要额外开启服务端口,产线禁止
- 同样针对Controller生产文档,和swagger相比,apiggs是基于代码解析生成的静态文档,对代码无入侵,而且不需要开启web服务,更安全更简便
介绍 lp-base-export
- 支持源码java文件解析,能拿到注释
- 支持字节码class文件解析,字节码解析是拿不到注释的
- 生成静态文档,无需开启web服务
- 对项目无入侵,在项目外通过maven插件或者ant等工具启动任务
- 生成文档样式全自定义,通过mvel表达式来解析生成
- 能递归获取父类属性
- 突破代码解析的最后壁垒(泛型),支持泛型解析后的泛型注入
- 能配合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. 拓展使用
- 配合Ant或者Gradle外部启动lp-base-export程序,分析当前项目代码再生成报告
- 封装成maven插件,通过maven插件调用,比如apiggs和enum-export-plugin
结语
起初该项目用于提取代码中枚举的注释然后批量生成接口注释,也可用于提取持久化对象字段的注释批量生成Mysql的字段的注释。最早是用Doclet来解析源码的,然后用ant来启动项目,其实这是相当不方便的,然后最近通过Javaparser来替代Doclet解析源码,减少了项目对jdk的依赖,同时通过提前暴露的思路攻克了泛型注入的问题,使得最后生成的对象更完整和更准确,同时也支持了内部类的解析。项目是独立项目,所以对分析的项目是零入侵,通过maven,gradle,ant等工具拉起项目,最后生成报告到指定目录。该项目我自己一直在使用,如果遇到问题可留言,我会单独联系给你解决。
如果这篇文章对你有帮助请给个star
image.png