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>

效果可以參考:

heaven 文檔目錄

ps: heaven 項目是個人整理了多年的工具包,幾百個類,手寫文檔估計要很久。

設計初衷

節約時間

Java 文檔一直是一個大問題。

很多項目不寫文檔,即使寫文檔,對於開發人員來說也是非常痛苦的。

不寫文檔的缺點自不用多少,手動寫文檔的缺點也顯而易見:

  1. 非常浪費時間,而且會出錯。

  2. 無法保證及時更新。程式碼已經變了,但是文檔還要同步修改。需要強制人來維護這一種一致性。這很難。

為什麼不是 swagger-ui

java 的文檔有幾類:

  1. jdk 自帶的 doc 生成。這個以前實踐給別人用過,別人用 C#,看到 java 的默認文檔感覺很痛苦。

就算是我們 java 開發者,也很討厭看 jdk 的文檔。看著不美觀,也很累。

  1. swagger-ui 是基於 java 註解的文檔生成工具。相對而言比較優雅,也非常強大。

但是缺點也是有的。開發人員要寫 jdk 原來的注釋+註解。註解太多,導致寫起來也很痛苦,大部分開發者後來還是選擇了放棄。

那麼問題來了?我們怎麼辦才能儘可能的讓開發人員,和文檔閱讀人員都樂於接受呢?

jdk 自帶的 doc 就是基於 maven 插件的,本項目也是。

區別如下:

  1. 儘可能的保證和 Java 原生注釋一致,讓開發者很容易就可以使用。

  2. 儘可能的資訊全面,但是文檔簡潔。讓文檔的閱讀者享受到等同於手寫文檔的體驗。

  3. 將資訊的獲取和生成區分開。方便用戶自己定義自己的輸出方式。

參數配置說明

為了更加靈活的實現文檔的生成和文檔元數據的生成,提供如下參數

插件配置屬性簡介

屬性 是否必填 說明 默認值 備註
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-全部文檔.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 屬性即可。

開源地址

//github.com/houbb/idoc

當然,這個項目還有很長的路要走。

如果喜歡,歡迎 fork star~

在這裡插入圖片描述