腦洞的由來
開發過程中經常遇到
- 將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 "測試輸出:" +
// "http://@{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