SpringBoot介面 - 如何生成介面文件之非侵入方式(通過註釋生成)Smart-Doc?

pdai 發表於 2022-07-15
Spring

通過Swagger系列可以快速生成API文件,但是這種API文件生成是需要在介面上新增註解等,這表明這是一種侵入式方式; 那麼有沒有非侵入式方式呢, 比如通過註釋生成文件? 本文主要介紹非侵入式的方式及整合Smart-doc案例。我們構建知識體系時使用Smart-doc這類工具並不是目標,而是要了解非侵入方式能做到什麼程度和技術思路。 @pdai

準備知識點

需要了解Swagger侵入性和依賴性, 以及Smart-Doc這類工具如何解決這些問題, 部分內容來自官方網站。@pdai

為什麼會產生Smart-Doc這類工具?

既然有了Swagger, 為何還會產生Smart-Doc這類工具呢? 本質上是Swagger侵入性和依賴性。

我們來看下目前主流的技術文件工具存在什麼問題:

  1. 侵入性強,需要編寫大量註解,代表工具如:swagger,還有一些公司自研的文件工具
  2. 強依賴性,如果專案不想使用該工具,業務程式碼無法編譯通過。
  3. 程式碼解析能力弱,使用文件不齊全,主要代表為國內眾多開源的相關工具。
  4. 眾多基於註釋分析的工具無法解析jar包裡面的註釋(sources jar包),需要人工配置原始碼路徑,無法滿足DevOps構建場景。
  5. 部分工具無法支援多模組複雜專案程式碼分析。

什麼是Smart-Doc?有哪些特性?

smart-doc是一款同時支援JAVA REST API和Apache Dubbo RPC介面文件生成的工具,smart-doc在業內率先提出基於JAVA泛型定義推導的理念, 完全基於介面原始碼來分析生成介面文件,不採用任何註解侵入到業務程式碼中。你只需要按照java-doc標準編寫註釋, smart-doc就能幫你生成一個簡易明瞭的Markdown、HTML5、Postman Collection2.0+、OpenAPI 3.0+的文件。

  • 零註解、零學習成本、只需要寫標準JAVA註釋。
  • 基於原始碼介面定義自動推導,強大的返回結構推導。
  • 支援Spring MVC、Spring Boot、Spring Boot Web Flux(controller書寫方式)、Feign。
  • 支援Callable、Future、CompletableFuture等非同步介面返回的推導。
  • 支援JavaBean上的JSR303引數校驗規範,包括分組驗證。
  • 對JSON請求引數的介面能夠自動生成模擬JSON引數。
  • 對一些常用欄位定義能夠生成有效的模擬值。
  • 支援生成JSON返回值示例。
  • 支援從專案外部載入原始碼來生成欄位註釋(包括標準規範釋出的jar包)。
  • 支援生成多種格式文件:Markdown、HTML5、Asciidoctor、Postman Collection、OpenAPI 3.0。 Up- 開放文件資料,可自由實現接入文件管理系統。
  • 支援匯出錯誤碼和定義在程式碼中的各種字典碼到介面文件。
  • 支援Maven、Gradle外掛式輕鬆整合。
  • 支援Apache Dubbo RPC介面文件生成。
  • debug介面除錯html5頁面完全支援檔案上傳,下載(@download tag標記下載方法)測試。

實現案例

從smart-doc 1.7.9開始官方提供了Maven外掛,可以通過在專案中整合smart-doc的Maven外掛,然後執行外掛直接生成文件。 我們的案例基於smart-doc-maven-plugin,生成文件。示例參考官方配置文件而寫。

配置

新增maven的外掛

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
        <plugin>
            <groupId>com.github.shalousun</groupId>
            <artifactId>smart-doc-maven-plugin</artifactId>
            <version>2.4.8</version>
            <configuration>
                <!--指定生成文件的使用的配置檔案,配置檔案放在自己的專案中-->
                <configFile>./src/main/resources/smart-doc.json</configFile>
                <!--指定專案名稱,推薦使用動態引數,例如${project.description}-->
                <!--如果smart-doc.json中和此處都未設定projectName,2.3.4開始,外掛自動採用pom中的projectName作為設定-->
                <!--<projectName>${project.description}</projectName>-->
                <!--smart-doc實現自動分析依賴樹載入第三方依賴的原始碼,如果一些框架依賴庫載入不到導致報錯,這時請使用excludes排除掉-->
                <excludes>
                    <!--格式為:groupId:artifactId;參考如下-->
                    <!--也可以支援正則式如:com.alibaba:.* -->
                    <exclude>com.alibaba:fastjson</exclude>
                </excludes>
                <!--includes配置用於配置載入外部依賴原始碼,配置後外掛會按照配置項載入外部原始碼而不是自動載入所有,因此使用時需要注意-->
                <!--smart-doc能自動分析依賴樹載入所有依賴原始碼,原則上會影響文件構建效率,因此你可以使用includes來讓外掛載入你配置的元件-->
                <includes>
                    <!--格式為:groupId:artifactId;參考如下-->
                    <!--也可以支援正則式如:com.alibaba:.* -->
                    <include>com.alibaba:fastjson</include>
                </includes>
            </configuration>
            <executions>
                <execution>
                    <!--如果不需要在執行編譯時啟動smart-doc,則將phase註釋掉-->
                    <phase>compile</phase>
                    <goals>
                        <!--smart-doc提供了html、openapi、markdown等goal,可按需配置-->
                        <goal>html</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

其中./src/main/resources/smart-doc.json是配置檔案。

{
  "serverUrl": "http://127.0.0.1", //伺服器地址,非必須。匯出postman建議設定成http://{{server}}方便直接在postman直接設定環境變數
  "pathPrefix": "", //設定path字首,非必須。如配置Servlet ContextPath 。@since 2.2.3
  "isStrict": false, //是否開啟嚴格模式
  "allInOne": true,  //是否將文件合併到一個檔案中,一般推薦為true
  "outPath": "D://md2", //指定文件的輸出路徑
  "coverOld": true,  //是否覆蓋舊的檔案,主要用於markdown檔案覆蓋
  "createDebugPage": true,//@since 2.0.0 smart-doc支援建立可以測試的html頁面,僅在AllInOne模式中起作用。
  "packageFilters": "",//controller包過濾,多個包用英文逗號隔開,2.2.2開始需要採用正則:com.test.controller.*
  "md5EncryptedHtmlName": false,//只有每個controller生成一個html檔案時才使用
  "style":"xt256", //基於highlight.js的程式碼高設定,可選值很多可檢視碼雲wiki,喜歡配色統一簡潔的同學可以不設定
  "projectName": "pdai-springboot-demo-smart-doc",//配置自己的專案名稱,不設定則外掛自動獲取pom中的projectName
  "skipTransientField": true,//目前未實現
  "sortByTitle":false,//介面標題排序,預設為false,@since 1.8.7版本開始
  "showAuthor":true,//是否顯示介面作者名稱,預設是true,不想顯示可關閉
  "requestFieldToUnderline":true,//自動將駝峰入參欄位在文件中轉為下劃線格式,//@since 1.8.7版本開始
  "responseFieldToUnderline":true,//自動將駝峰入參欄位在文件中轉為下劃線格式,//@since 1.8.7版本開始
  "inlineEnum":true,//設定為true會將列舉詳情展示到參數列中,預設關閉,//@since 1.8.8版本開始
  "recursionLimit":7,//設定允許遞迴執行的次數用於避免一些物件解析卡主,預設是7,正常為3次以內,//@since 1.8.8版本開始
  "allInOneDocFileName":"index.html",//自定義設定輸出文件名稱, @since 1.9.0
  "requestExample":"true",//是否將請求示例展示在文件中,預設true,@since 1.9.0
  "responseExample":"true",//是否將響應示例展示在文件中,預設為true,@since 1.9.0

  "ignoreRequestParams":[ //忽略請求引數物件,把不想生成文件的引數物件遮蔽掉,@since 1.9.2
    "org.springframework.ui.ModelMap"
  ],
  "dataDictionaries": [{ //配置資料字典,沒有需求可以不設定
    "title": "http狀態碼字典", //資料字典的名稱
    "enumClassName": "tech.pdai.springboot.smartdoc.constant.ResponseStatus", //資料字典列舉類名稱
    "codeField": "responseCode",//資料字典字典碼對應的欄位名稱
    "descField": "description"//資料字典物件的描述資訊字典
  }],
  "errorCodeDictionaries": [{ //錯誤碼列表,沒有需求可以不設定
    "title": "title",
    "enumClassName": "tech.pdai.springboot.smartdoc.constant.ResponseStatus", //錯誤碼列舉類
    "codeField": "responseCode",//錯誤碼的code碼欄位名稱
    "descField": "description"//錯誤碼的描述資訊對應的欄位名
  }],
  "revisionLogs": [{ //文件變更記錄,非必須
    "version": "1.1", //文件版本號
    "revisionTime": "2022-07-01 22:12:01", //文件修訂時間
    "status": "update", //變更操作狀態,一般為:建立、更新等
    "author": "pdai", //文件變更作者
    "remarks": "init user api" //變更描述
  },{ //文件變更記錄,非必須
    "version": "1.2", //文件版本號
    "revisionTime": "2022-07-01 22:12:02", //文件修訂時間
    "status": "update", //變更操作狀態,一般為:建立、更新等
    "author": "pdai", //文件變更作者
    "remarks": "add address api" //變更描述
  }
  ],
  "customResponseFields": [{ //自定義新增欄位和註釋,一般使用者處理第三方jar包庫,非必須
    "name": "code",//覆蓋響應碼欄位
    "desc": "響應程式碼",//覆蓋響應碼的欄位註釋
    "ownerClassName": "org.springframework.data.domain.Pageable", //指定你要新增註釋的類名
    "ignore":true, //設定true會被自動忽略掉不會出現在文件中
    "value": "00000"//設定響應碼的值
  }],
  "requestHeaders": [{ //設定請求頭,沒有需求可以不設定
    "name": "token",//請求頭名稱
    "type": "string",//請求頭型別
    "desc": "desc",//請求頭描述資訊
    "value":"token請求頭的值",//不設定預設null
    "required": false,//是否必須
    "since": "-",//什麼版本新增的改請求頭
    "pathPatterns": "/app/test/**",//請看https://gitee.com/smart-doc-team/smart-doc/wikis/請求頭高階配置?sort_id=4178978
    "excludePathPatterns":"/app/page/**"//請看https://gitee.com/smart-doc-team/smart-doc/wikis/請求頭高階配置?sort_id=4178978
  },{
    "name": "appkey",//請求頭
    "type": "string",//請求頭型別
    "desc": "desc",//請求頭描述資訊
    "value":"appkey請求頭的值",//不設定預設null
    "required": false,//是否必須
    "pathPatterns": "/test/add,/testConstants/1.0",//正規表示式過濾請求頭,url匹配上才會新增該請求頭,多個正則用分號隔開
    "since": "-"//什麼版本新增的改請求頭
  }],
  "requestParams": [ //設定公共引數,沒有需求可以不設定
    {
      "name": "configPathParam",//請求名稱
      "type": "string",//請求型別
      "desc": "desc",//請求描述資訊
      "paramIn": "path", // 引數所在位置 header-請求頭, path-路徑引數, query-引數
      "value":"testPath",//不設定預設null
      "required": false,//是否必須
      "since": "2.2.3",//什麼版本新增的該請求
      "pathPatterns": "/app/test/**",//請看https://gitee.com/smart-doc-team/smart-doc/wikis/請求高階配置?sort_id=4178978
      "excludePathPatterns":"/app/page/**"//請看https://gitee.com/smart-doc-team/smart-doc/wikis/請求高階配置?sort_id=4178978
    }],
  "responseBodyAdvice":{ //自smart-doc 1.9.8起,非必須項,ResponseBodyAdvice統一返回設定(不要隨便配置根據專案的技術來配置),可用ignoreResponseBodyAdvice tag來忽略
    "className":"tech.pdai.springboot.smartdoc.entity.ResponseResult" //通用響應體
  }
}

執行測試

可以通過Maven命令生成文件

//生成html
mvn -Dfile.encoding=UTF-8 smart-doc:html

在IDEA中,也可以通過maven外掛構建

SpringBoot介面 - 如何生成介面文件之非侵入方式(通過註釋生成)Smart-Doc?

maven構建日誌如下

[INFO] Scanning for projects...
[INFO] 
[INFO] --------------< tech.pdai:115-springboot-demo-smart-doc >---------------
[INFO] Building 115-springboot-demo-smart-doc 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] >>> smart-doc-maven-plugin:2.4.8:html (default-cli) > compile @ 115-springboot-demo-smart-doc >>>
[INFO] 
[INFO] --- maven-resources-plugin:3.2.0:resources (default-resources) @ 115-springboot-demo-smart-doc ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Using 'UTF-8' encoding to copy filtered properties files.
[INFO] Copying 0 resource
[INFO] Copying 1 resource
[INFO] 
[INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ 115-springboot-demo-smart-doc ---
[INFO] Nothing to compile - all classes are up to date
[INFO] 
[INFO] <<< smart-doc-maven-plugin:2.4.8:html (default-cli) < compile @ 115-springboot-demo-smart-doc <<<
[INFO] 
[INFO] 
[INFO] --- smart-doc-maven-plugin:2.4.8:html (default-cli) @ 115-springboot-demo-smart-doc ---
[INFO] ------------------------------------------------------------------------
[INFO] Smart-doc Start preparing sources at: 2022-07-01 22:43:54
[INFO] Artifacts that the current project depends on: ["org.springframework.boot:spring-boot-starter-web","org.springframework.boot:spring-boot-configuration-processor","org.projectlombok:lombok"]
[INFO] Smart-doc has loaded the source code path: [{"path":"D:/git/tech-pdai-spring-demos/115-springboot-demo-smart-doc/src/main/java"}]
[INFO] Smart-doc Starting Create API Documentation at: 2022-07-01 22:43:54
[INFO] API documentation is output to => D://md2
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  2.196 s
[INFO] Finished at: 2022-07-01T22:43:55+08:00
[INFO] ------------------------------------------------------------------------

構建後的html如下:

SpringBoot介面 - 如何生成介面文件之非侵入方式(通過註釋生成)Smart-Doc?

也可以看到還自動提供了mock的資料,以及測試介面的按鈕。還包含自定義的返回列舉型別等。

SpringBoot介面 - 如何生成介面文件之非侵入方式(通過註釋生成)Smart-Doc?

展示效果,可以參看https://api.doubans.com/

生成更多型別的文件

smart-doc 還支援生成如下型別的文件:

//生成markdown
mvn -Dfile.encoding=UTF-8 smart-doc:markdown
//生成adoc
mvn -Dfile.encoding=UTF-8 smart-doc:adoc
//生成postman json資料
mvn -Dfile.encoding=UTF-8 smart-doc:postman
// 生成 Open Api 3.0+, Since smart-doc-maven-plugin 1.1.5
mvn -Dfile.encoding=UTF-8 smart-doc:openapi

進一步理解

結合smart-doc官方文件,我們通過幾個問題進一步理解smart-doc。主要內容來源於官方文件

註釋資訊是有限的,smart-doc如何從註釋擴充文件內容呢?

我們知道註釋的資訊是有限的,swagger技術棧的方式通過定義註解來約束並擴充文件中的內容,那麼smart-doc如何從註釋擴充文件內容呢?

一方面smart-doc的實現初衷是通過使用javadoc文件註釋來去除註解式的侵入,因此smart-doc每增加一個功能首先都是去考慮javadoc原生的tag,

下面對smart-doc使用的一些javadoc的註釋tag做介紹。

tag名稱 使用描述
@param 對於在Spring Boot介面層,對於簡單型別的引數必須在使用@param時寫上註釋描述,對於Entity型別smart-doc則不會檢查
@deprecated 可以在註釋中用於標記介面已經廢棄,作用同@Deprecated註解
@apiNote @apiNoteJAVA新增的文件tag,smart-doc使用@apiNote的註釋作為方法的詳細描述,因此可以使用@apiNote來寫一段長註釋。如果一個方法不寫 @apiNote註釋說明,smart-doc直接使用方法預設註釋填充

另一方面,原生的tag是不夠的,所以smart-doc又通過自定義tag來支援更多功能的擴充

tag名稱 描述
@ignore @ignore tag用於過濾請求引數物件上的某個欄位,設定後smart-doc不輸出改欄位到請求引數列表中。關於響應欄位忽略的請看【忽略響應欄位】 如果@ignore加到方法上,則介面方法不會輸出到文件。從1.8.4開始@ignore支援新增到Controller上進行忽略不想生成文件的介面類。@ignore也可以用於方法上忽略某個請求引數。
@required 如果你沒有使用JSR303引數驗證規範實現的方式來標註欄位,就可以使用@required去標註請求引數物件的欄位,標註smart-doc在輸出引數列表時會設定為true
@mock smart-doc 1.8.0開始,@mock tag用於在物件基本型別欄位設定自定義文件展示值。設定值後smart-doc不再幫你生成隨機值。方便可以通過smart-doc直接輸出交付文件。
@dubbo smart-doc 1.8.7開始,@dubbo tag用於在DubboAPI介面類上新增讓smart-doc可以掃描到Dubbo RPC的介面生成文件。
@restApi smart-doc 1.8.8開始,@restApi tag用於支援smart-doc去掃描Spring Cloud Feign的定義介面生成文件。
@order smart-doc 1.9.4開始,@order tag用於設定Controller介面或者API入口的自定義排序序號,@order 1就表示設定序號為1
@ignoreResponseBodyAdvice smart-doc 1.9.8開始,@ignoreResponseBodyAdvice tag用於忽略ResponseBodyAdvice設定的包裝類。
@download smart-doc 2.0.1開始,@download tag用於標註在Controller的檔案下載方法上,生成debug頁面時可實現檔案下載測試。並且支援下載檔案帶請求頭引數測試。
@page smart-doc 2.0.2開始,@page tag用於標註在Controller的方法上表示該方法用來渲染返回一個靜態頁面,生成debug頁面時如果發起測試,測試頁面會自動在瀏覽器開啟新標籤顯示頁面。
@ignoreParams smart-doc 2.1.0開始,@ignoreParams tag用於標註在Controller方法上忽略掉不想顯示在文件中的引數,例如:@ignoreParams id name,多個引數名用空格隔開
@response smart-doc 2.2.0開始,@response tag標註在Controller方法上可以允許用這自己定義返回的json example。建議只在返回基礎型別時使用,如:Result<String>型別這種泛型是簡單原生型別的響應。
@tag @since 2.2.5, @tag用於將Controller方法分類, 可以將不同Contoller下的方法指定到多個分類下, 同時也可以直接指定Controller為一個分類或多個分類

Maven多模組中使用外掛有沒有比較好的實踐?

在獨立的Maven專案中使用smart-doc,當前可以說是如絲般爽滑。但是在Maven的多模組專案中使用smart-doc-maven-plugin時,很多同學就有疑問了, smart-doc外掛我到底是放在什麼地方合適?是放在Maven的根pom.xml中?還是說各個需要生成API介面文件的模組中呢? 下面就來說說根據不同的專案結構應該怎麼放外掛。

完全的父子級關係的maven專案:

├─parent
├──common
│   pom.xml
├──web1
│   pom.xml
├──web2
│   pom.xml
└─pom.xml

上面的maven結構假設是嚴格按照父子級來配置的,然後web1和web2都依賴於common, 這種情況下如果跑到web1下或者web2目錄下直接執行mvn命令來編譯 都是無法完成的。需要在根目錄上去執行命令編譯命令才能通過,而smart-doc外掛會通過類載入器去載入使用者配置的一些類,因此是需要呼叫編譯的和執行命令 是一樣的。這種情況下建議你建smart-doc-maven-plugin放到根pom.xml中,在web1和web2中放置各自的smart-doc.json配置。 然後通過-pl去指定讓smart-doc生成指定 模組的文件。操作命令如下:

# 生成web1模組的api文件
mvn smart-doc:markdown -Dfile.encoding=UTF-8  -pl :web1 -am
# 生成web2模組的api文件
mvn smart-doc:markdown -Dfile.encoding=UTF-8  -pl :web2 -am

如果不是按照嚴格父子級構建的專案,還是以上面的結構例子來說。common模組放在類parent中,但是common的pom.xml並沒有定義parent。 common模組也很少變更,很多公司內部可能就直接把common單獨depoly上傳到了公司的Nexus倉庫中,這種情況下web1和web2雖然依賴於common, 但是web1和web2都可以在web1和web2目錄下用命令編譯,這種情況下直接將smart-doc-maven-plugin單獨放到web1和web2中是可以做構建生成文件的。

多模組測試用例參考

注意: 怎麼去使用外掛並沒有固定的模式,最重要的是熟練Maven的一些列操作,然後根據自己的專案情況來調整。技巧嫻熟就能應對自如。 對於外掛的使用,從smart-doc-maven-plugin 1.2.0開始,外掛是能夠自動分析生成模組的依賴來載入必要的原始碼,並不會將所有模組的介面文件合併到一個文件中。

如果生成文件時遇到問題,該如何除錯?

在使用smart-doc-maven-plugin外掛來構建生成API文件的過程中可能會出現一些錯誤問題。官方文件中提供了除錯的方案:

  1. 新增smart-doc依賴

因為smart-doc-maven-plugin最終是使用smart-doc來完成專案的原始碼分析和文件生成的,
通常情況下真正的除錯的程式碼是smart-doc。但這個過程主要通過smart-doc-maven-plugin來排查。

<dependency>
     <groupId>com.github.shalousun</groupId>
     <artifactId>smart-doc</artifactId>
     <version>[最新版本]</version>
     <scope>test</scope>
</dependency>

注意: 使用smart-doc的版本最好和外掛依賴的smart-doc版本一致。

  1. 新增斷點

新增斷點如圖所示

SpringBoot介面 - 如何生成介面文件之非侵入方式(通過註釋生成)Smart-Doc?

  1. Debug模式執行構建目標

maven外掛在idea中執行debug非常簡單,操作如下圖。

SpringBoot介面 - 如何生成介面文件之非侵入方式(通過註釋生成)Smart-Doc?

這樣就可以直接進入斷點了。

提示: 上面是通過外掛去作為入口除錯smart-doc的原始碼,如果你想除錯外掛本身的原始碼執行過程,則將外掛的依賴新增到專案依賴中,如下:

<dependency>
    <groupId>com.github.shalousun</groupId>
    <artifactId>smart-doc-maven-plugin</artifactId>
    <version>【maven倉庫最新版本】</version>
</dependency>

然後通過上面的類似步驟除錯smart-doc-maven-plugin的原始碼

示例原始碼

https://github.com/realpdai/tech-pdai-spring-demos

https://smart-doc-group.github.io/

更多內容

告別碎片化學習,無套路一站式體系化學習後端開發: Java 全棧知識體系(https://pdai.tech)