java 文檔自動生成的神器 idoc
- 2021 年 10 月 15 日
- 筆記
寫文檔
作為一名開發者,每個人都要寫程式碼。
工作中,幾乎每一位開發者都要寫文檔。
因為工作是人和人的協作,產品要寫需求文檔,開發要寫詳細設計文檔,介面文檔。
可是,作為一個懶人,平時最討厭的一件事情就是寫文檔。
寫文檔最令我不爽的地方是在於程式碼備註要改一遍,然後文檔再改一遍。
所有重複的勞作,都是對於我們寶貴摸魚時間的最大浪費。
於是,我就常常想,能不能只寫一遍呢?
i-doc 項目簡介
idoc 為 java 項目生成項目文檔。
基於原生的 java 注釋,儘可能的生成簡介的文檔。用戶可以自定義自己的模板,生成自己需要的文檔。
實現原理:基於 maven 插件,類似於 javadoc。可以更加靈活,允許用戶自定義。
特性
(1)基於 maven 項目生成包含大部分資訊的元數據
(2)默認支援 markdown 簡化文檔的生成,支援自定義模板
(3)支援用戶自定義文檔生成器
(4)支援用戶自定生成文檔的類過濾器
(5)添加欄位類型別名,支援用戶自定義
快速入門
需要
jdk1.8+
maven 3.x+
maven 引入
使用 maven 引入當前 idoc 插件。
<build>
<plugins>
<plugin>
<groupId>com.github.houbb</groupId>
<artifactId>idoc-core</artifactId>
<version>0.3.0</version>
</plugin>
</plugins>
</build>
測試對象的創建
為了演示文檔,我們創建了一個 Address 對象。
package com.github.houbb.idoc.test.model;
/**
* 地址
* @author binbin.hou
* @since 0.0.1
*/
public class Address {
/**
* 城市
*/
private String country;
/**
* 街道
*/
private String street;
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
}
執行插件
mvn com.github.houbb:idoc-core:0.3.0:idoc
命令行日誌資訊
[INFO] ------------------------------------ Start generate doc
[INFO] 共計 【1】 個文件待處理,請耐心等待。進度如下:
==================================================================================================== 100%
[INFO] Generator doc with docGenerator: com.github.houbb.idoc.core.api.generator.ConsoleDocGenerator
[INFO] ------------------------------------ 文檔資訊如下:
[類名] com.github.houbb.idoc.test.model.Address
[類資訊] {"comment":"地址","docAnnotationList":[],"docFieldList":[{"comment":"城市","name":"country","type":"java.lang.String"},{"comment":"街道","name":"street","type":"java.lang.String"}],"docMethodList":[{"docMethodParameterList":[],"docMethodReturn":{"fullName":"java.lang.String","name":"String","packageName":"java.lang"},"docTagList":[],"exceptionList":[],"modifiers":["public"],"name":"getCountry","seeList":[],"signature":"getCountry()"},{"docMethodParameterList":[{"docAnnotationList":[],"name":"country","type":"java.lang.String"}],"docMethodReturn":{},"docTagList":[],"exceptionList":[],"modifiers":["public"],"name":"setCountry","seeList":[],"signature":"setCountry(country)"},{"docMethodParameterList":[],"docMethodReturn":{"fullName":"java.lang.String","name":"String","packageName":"java.lang"},"docTagList":[],"exceptionList":[],"modifiers":["public"],"name":"getStreet","seeList":[],"signature":"getStreet()"},{"docMethodParameterList":[{"docAnnotationList":[],"name":"street","type":"java.lang.String"}],"docMethodReturn":{},"docTagList":[],"exceptionList":[],"modifiers":["public"],"name":"setStreet","seeList":[],"signature":"setStreet(street)"}],"docTagList":[{"lineNum":5,"name":"author","parameters":["binbin.hou"],"value":"binbin.hou"},{"lineNum":6,"name":"since","parameters":["0.0.1"],"value":"0.0.1"}],"fullName":"com.github.houbb.idoc.test.model.Address","modifiers":["public"],"name":"Address","packageName":"com.github.houbb.idoc.test.model"}
[INFO] ------------------------------------ Finish generate doc
更多生成方式
當然,你可以發現這裡只是把元數據進行輸出到控台,意義不大。
我們可以根據需求,自定義實現生成類。
比如下面的方式,可以使用內置的 MarkdownDocGenerator
輸出到 markdown 文件。
<plugin>
<groupId>com.github.houbb</groupId>
<artifactId>idoc-core</artifactId>
<version>0.3.0</version>
<configuration>
<generates>
<generate>com.github.houbb.idoc.ftl.api.generator.MarkdownDocGenerator</generate>
</generates>
</configuration>
<dependencies>
<dependency>
<groupId>com.github.houbb</groupId>
<artifactId>idoc-ftl</artifactId>
<version>0.3.0</version>
</dependency>
</dependencies>
</plugin>
效果可以參考:
ps: heaven 項目是個人整理了多年的工具包,幾百個類,手寫文檔估計要很久。
設計初衷
節約時間
Java 文檔一直是一個大問題。
很多項目不寫文檔,即使寫文檔,對於開發人員來說也是非常痛苦的。
不寫文檔的缺點自不用多少,手動寫文檔的缺點也顯而易見:
-
非常浪費時間,而且會出錯。
-
無法保證及時更新。程式碼已經變了,但是文檔還要同步修改。需要強制人來維護這一種一致性。這很難。
為什麼不是 swagger-ui
java 的文檔有幾類:
- jdk 自帶的 doc 生成。這個以前實踐給別人用過,別人用 C#,看到 java 的默認文檔感覺很痛苦。
就算是我們 java 開發者,也很討厭看 jdk 的文檔。看著不美觀,也很累。
- swagger-ui 是基於 java 註解的文檔生成工具。相對而言比較優雅,也非常強大。
但是缺點也是有的。開發人員要寫 jdk 原來的注釋+註解。註解太多,導致寫起來也很痛苦,大部分開發者後來還是選擇了放棄。
那麼問題來了?我們怎麼辦才能儘可能的讓開發人員,和文檔閱讀人員都樂於接受呢?
jdk 自帶的 doc 就是基於 maven 插件的,本項目也是。
區別如下:
-
儘可能的保證和 Java 原生注釋一致,讓開發者很容易就可以使用。
-
儘可能的資訊全面,但是文檔簡潔。讓文檔的閱讀者享受到等同於手寫文檔的體驗。
-
將資訊的獲取和生成區分開。方便用戶自己定義自己的輸出方式。
參數配置說明
為了更加靈活的實現文檔的生成和文檔元數據的生成,提供如下參數
插件配置屬性簡介
屬性 | 是否必填 | 說明 | 默認值 | 備註 |
---|---|---|---|---|
encoding | 否 | 項目編碼 | UTF-8 | |
includes | 否 | 元數據包含的文件資訊 | **\/*.java |
默認掃描所有 java 文件 |
excludes | 否 | 元數據排除的文件資訊 | 無 | 默認不排除 |
isOverwriteWhenExists | 否 | 文檔存在時是否覆蓋 | true | |
isAllInOne | 否 | 所有類資訊是否生成單個文檔 | true | 命令行文檔生成器,此屬性無意義。 |
generates | 否 | 文檔生成類 | 命令行文檔生成資訊 | 可以同時指定多個。類名全稱。用戶自定義參見 com.github.houbb.idoc.api.core.genenrator.IDocGenerator |
generateFilters | 否 | 文檔生成類過濾器 | 無 | 可以同時指定多個。類名全稱。用戶自定義參見 com.github.houbb.idoc.api.core.filter.IDocGenerateFilter |
targetDir | 否 | 生成目標文件目錄 | 無 | 自定義指定文檔生成的文件夾 |
isAllInOne
簡單的文檔,建議直接生成在一個文件。
如果較為複雜,則可以設為 false,則會按照
generates 相關問題
默認的命令行文檔,主要用於演示和資訊查看,不具有實際意義。
建議引入 idoc-ftl
模組,使用 MarkdownDocGenerator
生成器。
可以同時指定多個。
可引入 idoc-api
自行定義。
generateFilters 建議
實際的文檔,主要關心定義的方法。
我們可以針對 DocClass 的包名,過濾只生成 Service 方法文檔。
如果是在以前的基礎上,則可以加上 @since
@version
等資訊的過濾。
可以同時指定多個。
可引入 idoc-api
自行定義。
自定義 Filter
可以參考當前項目的 idoc-test
模組。
整體配置如下:
<build>
<plugins>
<plugin>
<groupId>com.github.houbb</groupId>
<artifactId>idoc-core</artifactId>
<version>0.3.0</version>
<configuration>
<isAllInOne>true</isAllInOne>
<generates>
<generate>com.github.houbb.idoc.ftl.api.generator.MarkdownDocGenerator</generate>
</generates>
<generateFilters>
<generateFilter>com.github.houbb.idoc.test.filter.MyGenerateFilter</generateFilter>
</generateFilters>
</configuration>
<dependencies>
<dependency>
<groupId>com.github.houbb</groupId>
<artifactId>idoc-test</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
指定文檔生成器
指定使用 Markdown 文檔生成器,可以同時指定多個。
<generates>
<generate>com.github.houbb.idoc.ftl.api.generator.MarkdownDocGenerator</generate>
</generates>
引入包
MarkdownDocGenerator 在 idoc-ftl
模組中,需要引入對應的依賴。
當然 idoc-core
默認依賴 idoc-ftl
。
指定文件生成類的過濾器
如果不定義自己的類生成過濾器,則會生成所有的類資訊。
一般使用中我們只關心 service 方法,所以添加了類的過濾實現。
實現如下:
引入 idoc-api 包
<dependency>
<groupId>com.github.houbb</groupId>
<artifactId>idoc-api</artifactId>
<version>${project.version}</version>
</dependency>
實現 IDocGenerateFilter
package com.github.houbb.idoc.test.filter;
import com.github.houbb.idoc.api.core.filter.IDocGenerateFilter;
import com.github.houbb.idoc.api.model.metadata.DocClass;
/**
* 自定義生成過濾器
* @author binbin.hou
* @since 0.0.1
*/
public class MyGenerateFilter implements IDocGenerateFilter {
@Override
public boolean include(DocClass docClass) {
if("QueryUserService".equalsIgnoreCase(docClass.getName())) {
return true;
}
return false;
}
}
插件中配置使用
<generateFilters>
<generateFilter>com.github.houbb.idoc.test.filter.MyGenerateFilter</generateFilter>
</generateFilters>
注意,也需要將你定義這個過濾器的 jar 添加依賴,否則無法找到對應的類資訊。
<dependencies>
<dependency>
<groupId>com.github.houbb</groupId>
<artifactId>idoc-test</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
類程式碼資訊
User 資訊
/**
* 用戶資訊
* @author binbin.hou
* @since 0.0.1
*/
public class User {
/**
* 名稱
* @require 是
* @remark 中文名稱,請認真填寫
*/
private String name;
/**
* 年齡
*/
private int age;
/**
* 生日
*/
private Date birthday;
/**
* 地址
*/
private List<Address> addressList;
/**
* 伴侶
*/
private User mate;
//...
}
i-doc 定義的標籤
@require
表示當前欄位是否必填,作為方法入參時。
@remark
表示當前欄位的備註資訊。
方法類資訊
- QueryUserService.java
/**
* 查詢用戶服務類
* @author binbin.hou
* @since 0.0.1
*/
public interface QueryUserService {
/**
* 根據用戶資訊查詢用戶
* @param user 用戶資訊
* @return 結果
* @since 0.0.2,2019/02/12
*/
public User queryUser(final User user);
}
執行插件
mvn com.github.houbb:idoc-core:0.3.0:idoc
- 日誌資訊
[INFO] ------------------------------------ Start generate doc
[INFO] 共計 【4】 個文件待處理,請耐心等待。進度如下:
==================================================================================================== 100%
[INFO] Generator doc with docGenerator: com.github.houbb.idoc.ftl.api.generator.MarkdownDocGenerator
[INFO] Markdown 生成文檔文件 all in one 路徑: /Users/houbinbin/code/_github/idoc/idoc-test/src/main/resources/idoc-gen/idoc-test-全部文檔.md
[INFO] ------------------------------------ Finish generate doc
文檔資訊
當前文件路徑日誌會列印,比如我自己測試的為:
/Users/houbinbin/code/_github/idoc/idoc-test/src/main/resources/idoc-gen/idoc-test-全部文檔.md
文檔生成效果
參見文檔:
欄位類型別名支援
可以參考當前項目的 idoc-test
模組。
為什麼需要
有時候頁面顯示類型,希望更加友好。
所以系統內置了一些別名顯示,也同時支援自定義別名。
類型欄位的別名
系統內置
系統當前版本提供了常見的別名。
詳情見 com.github.houbb.idoc.core.util.JavaTypeAliasUtil
類型 | 別稱 |
---|---|
java.lang.Float | 浮點型 |
java.lang.Double | 浮點型 |
java.util.Date | 日期 |
java.time.LocalDateTime | 日期時間 |
java.util.Currency | 貨幣 |
float | 浮點型 |
java.lang.Integer | 整型 |
long | 長整型 |
java.math.BigDecimal | 數字 |
java.lang.Character | 字元 |
java.lang.Long | 長整型 |
java.lang.Short | 短整型 |
java.util.Map | 映射 |
java.time.LocalTime | 時間 |
java.lang.Boolean | 布爾值 |
java.math.BigInteger | 數字 |
java.lang.String | 字元串 |
java.lang.Byte | 位元組 |
double | 浮點型 |
byte | 位元組 |
java.util.Collection | 集合 |
int | 整型 |
java.util.List | 列表 |
boolean | 布爾值 |
java.time.LocalDate | 日期 |
char | 字元 |
short | 短整型 |
void | 空 |
array | 數組 |
自定義的方式
可以通過 typeAlias 指定自定義的欄位別稱。
<configuration>
<generateFilters>
<generateFilter>com.github.houbb.idoc.test.filter.MyGenerateFilter</generateFilter>
</generateFilters>
<isAllInOne>true</isAllInOne>
<typeAliases>
<typeAlias>
<key>java.lang.String</key>
<value>String自定義說明</value>
</typeAlias>
</typeAliases>
</configuration>
優先順序
用戶自定義的欄位別名優先順序高於系統默認。
後面定義的別名會直接覆蓋前面的別名。
測試程式碼演示
對象定義
/**
* 別名測試
* @author binbin.hou
* @since 0.0.1
*/
public class TypeAliasSimpleBean {
/**
* 名稱
*/
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
測試日誌
運行測試日誌如下:
{"comment":"別名測試","docAnnotationList":[],"docFieldList":[{"comment":"名稱","name":"name","type":"java.lang.String","typeAlias":"String自定義說明"}],"docMethodList":[{"docMethodParameterList":[],"docMethodReturn":{"fullName":"java.lang.String","name":"String","packageName":"java.lang"},"docTagList":[],"exceptionList":[],"modifiers":["public"],"name":"getName","seeList":[],"signature":"getName()"},{"docMethodParameterList":[{"docAnnotationList":[],"name":"name","type":"java.lang.String","typeAlias":"String自定義說明"}],"docMethodReturn":{},"docTagList":[],"exceptionList":[],"modifiers":["public"],"name":"setName","seeList":[],"signature":"setName(name)"}],"docTagList":[{"lineNum":5,"name":"author","parameters":["binbin.hou"],"value":"binbin.hou"},{"lineNum":6,"name":"since","parameters":["0.0.1"],"value":"0.0.1"}],"fullName":"com.github.houbb.idoc.test.model.TypeAliasSimpleBean","modifiers":["public"],"name":"TypeAliasSimpleBean","packageName":"com.github.houbb.idoc.test.model"}
其中 typeAlias 就是欄位類型的別名,我們可以用來更加友好的顯示欄位資訊。
其他的思考
自定義方式的便利性
自定義的方式採用基於 xml 的方式是比較方便。
但是數量比較多的時候就沒有那麼方便,本來考慮添加對應的配置屬性介面,權衡下還是使用了 xml 配置的方式。
是否使用 comment 資訊?
如果一個欄位,沒有指定別名,是否使用 comment 資訊做替代?
建議使用,當前版本不做處理。
- 為什麼使用
比起冗長的類資訊,大部分人更樂於看到解釋。
如果是針對同構的系統(都是 java 語言),則可以理解。
如果是針對異構的系統(比如前台是 php),則不易於理解。
- 為什麼不處理
大部分的介面都是常見欄位, 性價比不高。
可能存在欄位沒有些 comment 的情況,會導致判斷的複雜性。
如果用戶不想使用別名
直接修改模板即可,使用原來的欄位 type
屬性即可。
開源地址
當然,這個項目還有很長的路要走。
如果喜歡,歡迎 fork star~