health4j—Java項目的全麵體檢工具
最近利用業餘時間寫了一個Java代碼靜態分析工具的聚合器。集成了三種主流的靜態分析工具:pmd,checkstyle,findbugs。可以用這三種工具提供的幾千種規則集,來給你的項目進行全麵體檢,同時附帶了歸納整理並提供郵件通知。代碼開源在github上,取名為health4j。
開發這個工具的初衷是希望它能約束自己代碼的規範性。同時,引導自己采用一些已被業界認可的“最佳實踐”,保證至少自己的代碼更加“health”。寫它的另一個目的,是繼續我的個人愛好——“自動化”工具。
代碼的整潔、規範,對於一個軟件項目的重要性不言而喻。特別是那些曾經維護過別人遺留代碼的人,相信你們都深有感觸。但不要說別人,即便是我們自己寫的代碼,隔個三五個月自己再回頭看,有時也是不知所雲。當然對於代碼缺陷而言,更可能會成為影響項目健壯性的一個定時炸彈。因此我認為,此舉至少對我個人而言是有利的。
分析工具簡介
這三個Java界的主流靜態代碼分析工具的對比:
需要說明的是,checkstyle主要用於檢查代碼書寫規範。pmd,findbugs則是非常出名的缺陷分析工具。它們收集了非常多的檢查規則和缺陷模式。其中pmd用於分析java源碼,而findbugs分析的是java編譯後的字節碼,jtest是商業工具,目前並未集成。
設計
邏輯結構圖:
該項目目前提供了三個分析工具的實現。每個工具都被拆分為三個部件:EnvVerifier(環境驗證器),CommandInvoker(命令執行器),ReportExtractor(報告提取器)。每個工具的執行,在內部都會觸發這三個部件的依次執行。並且每個工具都被內聚為一個任務,以供在線程池中並發執行。
每個工具的運行,都會伴隨著輸出自己的xml格式的報表文件。根據配置文件中是否啟用了合並器,來決定是否需要對每個工具輸出的報表文件作解析並提取(ReportExtractor),以供下麵的聚合器聚合之用。
如果開啟了聚合器,從每個工具輸出的報表文件中,提取出公共的數據信息(來自於每個工具數據信息的抽象),然後根據給定的聚合模板,生成一份聚合後的html報表。
如果開啟了通知器,那將會根據通知器的實現(默認給出的是郵件通知),將聚合後的html發送給目標。
類圖結構:
從代碼的設計圖中可看出,每個動作都有與之對應的接口抽象。因此雖然隻提供了三個工具的實現,但它仍然有良好的擴展性。隻需提供相應的接口實現,並定義好需要的配置文件信息,可以添加更多的分析工具。在每個工具的實現上,添加圖中的Tool注解,並標明其名稱(name屬性)。health4j在運行時,會自動掃描到該實現,並將其加入到線程池中執行。
Java SE的服務加載器
在開發web項目的時候,我們通常會引入spring來作為Ioc容器,這樣我們可以將抽象與具體的實現隔離。但對於一個小的standalone項目,這麼做似乎有些殺雞用牛刀的感覺。不過幸好JDK自1.6版本後就提供了SPI(ServiceProvider
Interface)的默認實現。有了它,你在寫一些小工具的時候,不用spring這麼“重”的bean容器,也可以實現Ioc。具體的做法很簡單。它提供了一個ServiceLoader的泛型類。會從一個指定的位置加載對某個Service(該Service通常被定義為一個接口或抽象類)實現的配置。
比如這裏的兩個服務接口:ReportMerger,ReportNotifier。我們首先在一個項目的資源文件夾內創建一個名為“META-INF.services”文件夾。然後裏麵創建兩個配置文件。每個配置文件都以接口的完全限定名作為文件名稱:
而每個文件的內容也異常簡單,你隻需要指定該服務接口的提供者(實現類)的完全限定名即可:
com.freedom.health4j.api.impl.common.DefaultMerger
這樣在獲取該接口提供者的時候,我們便無需顯示將該提供者的實例化過程與該服務綁定。示例:
private static void merge(ReportInfo reportInfo) { if (Boolean.valueOf(commonConfig.getProperty(Constants.COMMON_ENABLE_MERGE_KEY))) { ServiceLoader<ReportMerger> serviceLoader = ServiceLoader.load(ReportMerger.class); Iterator<ReportMerger> mergerIterator = serviceLoader.iterator(); if (!mergerIterator.hasNext()) { throw new RuntimeException("can not load service provider for service : ReportMerger"); } ReportMerger merger = mergerIterator.next(); merger.setCommonConfig(commonConfig); merger.merge(reportInfo); } }
雖然這種方式不及Spring等專業Ioc容器那樣強大。不過對於開發一些工具類的小應用而言,卻是非常簡單並且實用。
使用與集成
該工具可作為獨立的jar執行,也可以構建為unix-like-service(見)。當然作為自動化的一部分,我們仍然希望有非人為因素之外的其他觸發條件。這裏,可以列舉幾個常用的觸發條件:
- 時間觸發:利用linux定時任務在指定的時間觸發
- 事件觸發:在VCS等代碼版本控製軟件的hook中觸發
- 持續集成:通過maven/ant等構建工具的task觸發

綜述
最後更新:2017-11-22 18:33:45