大家好,我是胡曉宇,目前在云效主要負責Flow流水線編排、任務調度與執行引擎相關的作業,
作為一個有多年Java開發測驗工具鏈開發經驗的CRUD專家,使用過所有主流的Java構建工具,對于如何高效使用Java構建工具沉淀了一套方法,眾所周知,當前最主流的Java構建工具為Maven/Gradle/Bazel,針對每一個工具,我將分別從日常作業中常見的場景問題切入,例如依賴管理、構建加速、靈活開發、高效遷移等,針對性地介紹如何高效靈活地用好這3個工具,

Java構建工具的前世今生
在上古時代,Java的構建都在使用make,撰寫makefile來進行Java構建有非常多別扭與不便的地方,
緊接著Apache Ant誕生了,Ant可以靈活的定義清理編譯測驗打包等程序,但是由于沒有依賴管理的功能,以及需要撰寫復雜的xml,還是存在著諸多的不便,
隨后Apache Maven誕生了,Maven是一個依賴項管理和構建自動化工具,遵循著約定大于配置的規則,雖然也需要撰寫xml,但是對于復雜工程更加容易管理,有著標準化的工程結構,清晰的依賴管理,此外,由于Maven本質上是一個插件執行框架,也提供了一定的開放性的能力,我們可以通過Maven的插件開發,為構建構成創造一定的靈活性,
但是由于采用約定大于配置的方式,喪失了一定的靈活性,同時由于采用xml管理構建程序與依賴,隨著工程的膨脹,配置管理還是會帶來不小的復雜度,在這個背景下,集合了Ant與Maven各自優勢的Gradle誕生了,
Gradle也是一個集合了依賴管理與構建自動化的工具,首要的他不再使用XML而是基于Groovy的DSL來描述任務串聯起整個構建程序,同時也支持插件提供類似于Maven基于約定的構建,除了在構建依賴管理上的諸多優勢之外,Gradle在構建速度上也更具優勢,提供了強大的快取與增量構建的能力,
除了以上Java構建工具之外,Google在2015年開源了一款強大,但上手難度較大的分布式構建工具Bazel,具有多語言、跨平臺、可靠增量構建的特點,在構建上可以成倍提高構建速度,因為它只重新編譯需要重新編譯的檔案,Bazel也提供了分布式遠程構建和遠程構建快取兩種方式來幫助提升構建速度,
目前業內使用Ant的人已經比較少,主要都在用Maven、Gradle和Bazel,如何真正基于這三款工具的特點發揮出他們最大的效用,是這個系列文章要幫大家解決的問題,先從Maven說起,
優雅高效地用好Maven
當我們正在維護一個Maven工程時,關注以下三個問題,可以幫助我們更好的使用Maven,
● 如何優雅的管理依賴
● 如何加速我們的構建測驗程序
● 如何擴展我們自己的插件
優雅的依賴管理
在依賴管理中,有以下幾個實踐原則,可以幫助我們優雅高效的實作不同場景下的依賴管理,
● 在父模塊中使用dependencyManagement,配置依賴
● 在子模塊中使用dependencies,使用依賴
● 使用profiles,進行多環境管理
以我在日常開發中維護的一個標準的spring-boot多模塊Maven工程為例,

工程內各個module之間的依賴關系如下,通常這也是標準的 spring-boot restful api多模塊工程的結構,

便捷的依賴升級
通常我們在依賴升級的時候會遇到以下問題:
● 多個依賴關聯升級
● 多個模塊需要一起升級
在父模塊的pom.xml中,我們配置了基礎的spring-boot依賴,也配置了日志輸出需要的logback依賴,可以看出,我們遵循了以下的原則:
(1)在所有子模塊的父模塊中的pom中配置dependencyManagement,統一管理依賴版本,在子模塊中直接配置依賴,不用再糾纏于具體的版本,避免潛在的依賴版本沖突,
(2)把groupId相同的依賴,配置在一起,比如groupId為org.springframework.boot,我們配置在了一起,
(3)把groupId相同,但是需要一組依賴共同提供功能的artifactId,配置在一起,同時將版本號抽取成變數,便于后續一組功能共同的版本升級,比如spring-boot依賴的版本抽取成了spring-boot.version,


在子模塊build-engine-api的pom.xml中,由于在父pom中配置了 dependencyManagement中依賴的spring-boot相關依賴的版本,因此在子模塊的pom中,只需要在dependencies中直接宣告依賴,確保了依賴版本的一致性,

合理的依賴范圍
Maven依賴有依賴范圍(scope)的定義,compile/provieded/runtime/test/system/import,原則上,只按照實際情況配置依賴的范圍,在必要的階段,只引入必要的依賴,
90%的Java程式員應該都使用過org.projectlombok:lombok來簡化我們的代碼,其原理就是在編譯程序中將注解轉化為Java實作,因此該依賴的scope為provided,也就是編譯時需要,但在構建出最終產物時又需要被排除,

當你的代碼需要使用jdbc連接一個mysql資料庫,通常我們會希望針對標準 JDBC 抽象進行編碼,而不是直接錯誤的使用 MySQL driver實作,這個時候依賴的scope就需要設定為runtime,這意味著我們在編譯時無法使用該依賴,該依賴會被包含在最終的產物中,在程式最終執行時可以在classpath下找到它,

在子模塊dao中,我們有對sql進行測驗的場景,需要引入記憶體資料庫h2,
因此,我們將h2的scope設定為test,這樣我們在測驗編譯和執行時可以使用,同時避免其出現在最終的產物中,

更多關于scope的使用,可以參考官方幫助檔案,
多環境支持
舉個簡單的例子,當我們的服務在公有云部署時,我們使用了一個云上版本為8.0的MySQL,而當我們要進行專有云部署時,用戶提供一個自運維的版本為5.7的MySQL,因此,我們在不同的環境中使用不同的 mysql:mysql-connector-java 版本,
類似的,在專案實際的開發程序中,我們經常會面臨同一套代碼,在多套環境中部署,存在部分依賴不一致的情況,

關于profiles的更多用法,可以參考官方幫助檔案
依賴糾錯
如果你已經在父pom中使用dependencyManagement來鎖定依賴版本,大概率的,你幾乎很少會碰到依賴沖突的情況,
但是當你還是意外的看到了NoSuchMethodError,ClassNotFoundException 這兩個例外的時候,有以下兩個方法可以快速的幫你糾錯,
(1)通過依賴分析找到沖突的依賴

(2)通過添加stdout代碼找到沖突的類實際是從哪個依賴中查找的

通過具體的路徑中對應的版本資訊,找到對應的版本并校正,

當然這個方法也可以糾出一些依賴被錯誤的加載到classpath下,非工程本身依賴配置引起的沖突,
測驗構建程序加速
作為一個開發者,總會希望我們的工程無論在什么情況下,執行的又快又穩,那么在Maven的使用程序中,需要遵循以下原則,
● 盡可能復用快取
● 盡可能的并行構建或測驗
依賴下載加速
通常情況下,根據Maven組態檔 ${user.home}/.m2/settings.xml 中的配置,默認情況下是快取在${user.home}/.m2/repository/,

通常在構建程序中,依賴的下載往往會成為比較耗時的部分,但是通過一些簡單的設定,我們可以有效的減少依賴的下載與更新,
● 優化updatePolicy設定
updatePolicy指定了嘗試更新的頻率,Maven 會將本地 POM 的時間戳(存盤在存盤庫的 maven-metadata 檔案中)與遠程進行比較,選項包括:always(總是)、daily(每天,默認值)、interval:X(其中 X 是以分鐘為單位的整數)、never(從不),

● 使用離線構建
除此之外,如果構建環境已經存在快取,可以使用Maven的offline模式進行構建,避免依賴或插件的下載更新,
直觀的,日志中將不會出現類似如下Downloading相關的資訊,

構建程序加速
在默認情況下,Maven構建的程序并不會充分的使用你的硬體的全部能力,他會順序的構建你的maven工程的每一個模塊,這個時候,如果可以使用并行構建,那么將有機會提升構建速度,

以上是并行構建的兩個命令,可以根據實際的cpu情況來選擇對應的命令,但是如果你發現構建時間并沒有得到減少,那么你的maven模塊間可能存在類似的依賴,模塊之間只是一個簡單的傳遞,

那么并行構建對你來說并不適用,如果你的模塊間依賴關系存在并行的可能,那么使用上述命令進行構建,才能使并行構建發揮效果,

測驗程序加速
當我們嘗試加速maven工程測驗用例的部分,那么就不得不提到一個插件,maven-surefire-plugin,
當你在執行mvn test的時候,默認情況下就是surefire插件在作業,如果我們想在測驗中使用并行的能力,可以作如下配置,

但是需要注意不恰當的使用并行能力進行測驗,反而可能帶來副作用,比如當parallel配置為methods,但是由于某些原因測驗用例的執行之間存在順序要求,反而會出現因為用例方法并行執行,導致用例失敗,因此也倒逼我們,如果想獲得更快的測驗速度,case的撰寫也需要獨立且高效,
更多關于surefire插件的使用,可以參考這篇檔案,
Maven插件開發
maven本質上是一個插件執行框架,所有的執行程序,都是由一個一個插件獨立完成的,關于maven的核心插件可以參考這篇檔案,
maven默認為我們提供的這些插件比如maven-install-plugin/mvn-surefire-plugin/mvn-deploy-plugin外,還有一些三方提供的插件,單測覆寫率插件mvn-jacoco-plugin,生成api檔案的swagger-maven-plugin等等,
在日常作業的程序中,我碰到了這樣一個問題:有個存在明顯問題的sql被發布到了預發布環境,同時由于預發與生產使用的是同一個db實體,由于sql的性能問題,影響了線上,
除了通過必要的code review準入,來避免類似的問題,更簡單的,我們可以自己動手實作一個代碼中sql掃描的插件,讓代碼在CI時直接失敗掉,自動化的避免此類問題的發生,于是我們開發了一個maven插件,使用方法和效果如下:
在工程中引入我們開發并部署好的插件com.aliyun.yunxiao:mybatis-sql-scan,

執行以下命令,或其他包含validate階段執行的命令,

我們將會在日志中看到如下插件執行的資訊

在掃描出缺陷時,build失敗,并會在日志中出現對應的資訊:
在GlobalLockMapper.java這個檔案中,我們有一條全表掃描的sql陳述句可能存在風險,

同時build失敗,

接下來我會從如何開發這個例外sql掃描的maven插件入手,幫助大家了解插件開發的程序,
1、創建工程

生成的sample工程如下,

其中MyMojo.java定義了插件的入口實作,
此外在根pom.xml中可以看到,
● packaging為“maven-plugin”,
● 依賴配置中,依賴了一些插件開發的基礎二方庫,
● 插件節點下,依賴了maven-plugin-plugin協助我們完成插件的構建,


2、Mojo實作
在開始實作我們的Mojo之前,我們需要做如下分析:
● 插件在maven的哪個生命周期執行
● 插件在執行時需要哪些入口引數
● 插件執行完成后怎么退出
由于我們要實作的插件是要做mybatis annotation掃描比如 @Update/@Select,判斷是否有例外的sql,比如是否存在全表掃描的sql,是否存在全表更新的sql等,對于此種場景下,
● 由于需要掃描特定的原始碼,需要知道工程原始碼的所在目錄,以及掃描哪些檔案
● 插件掃描出例外時,只要報錯即可,不用產出任何報告
● 希望在后續執行mvn validate時觸發掃描
那么預期中的插件是這樣的,

那么,
● @Mojo(name = "check") 定義了goal
● @Parameter
○ @Parameter(defaultValue = "https://www.cnblogs.com/yyds114/p/${project}", readonly = true) 引數系結了工程的根目錄 ,project.getCompileSourceRoots()便可以獲取到源代碼的根路徑
○ 我們定義了mapperFiles,用來負責掃描哪些檔案的通配,excludeFiles用來負責排除哪些檔案
● execute()
○ 有了以上的基礎,在execute方法中我們便可以實作對應的邏輯,當掃描結出例外的sql時,拋出MojoFailureException例外,插件便會失敗終止,

以上,我們便完成了一個插件的基本能力的開發,
3、插件的打包與上傳
插件開發完成后,我們可以通過配置distributionManagement,然后執行mvn deploy,完成插件的構建與發布,

希望通過我的介紹,能夠幫助大家更好的使用maven,下一篇我們講Gradle,歡迎持續關注我們,
點擊下方鏈接,即可免費體驗云效流水線Flow,
https://www.aliyun.com/product/yunxiao/flow?channel=yy_practice

轉載請註明出處,本文鏈接:https://www.uj5u.com/gongcheng/454665.html
標籤:其他
