脑洞的由来
开发过程中经常遇到
- 将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();
效果如下图:
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();
效果如下图:
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