如何將私有的 JAR 檔加入到由 Apache Maven 管理的 Java 專案中
我們最近有個新的 Java 專案,客戶提供原始碼之後,卻發現他們自己架設的 Maven Repository 並沒有開啟防火牆讓我們連線,以致於專案無法下載相依套件而無法建置。但除了開防火牆連線外,其實還有很多方法可以讓你獲取 Maven 所需的相依套件。今天這篇文章,我就來分享關於 Maven 如何管裡相依套件,以及如何正確的將私有的第三方 JAR 檔加到專案內,降低團隊取得這些 JAR 檔的門檻。
其實我們已經不是只有一次需要將第三方的 JAR 檔(3rd-party JARs)加入到 Maven 的相依套件中,但是這種想法其實跟 Apache Maven 希望達到的目的有一點點衝突。
如果直接使用 IntelliJ IDEA、Eclipse 或 VSCode 內建的 Build system 來建置專案,要加入一個 JAR 檔到專案中其實非常容易。然而就算你不用任何 Build tools 也可以在透過 javac 編譯 Java 程式時加入 -classpath 參數,直接參考特定的 JAR 檔也很基本。但是當你用了 Apache Maven 來幫你管裡相依套件後,它把 Project 與 JAR file 之間做了一層抽象,透過一種所謂 Dependency (相依套件) 的概念來串聯兩者之間,你必須理解這個概念,才能很清楚的知道如何更輕易的管裡相依關係。
當你在使用 Apache Maven 的時候,它有一套相當完整的 Dependency Mechanism (相依管裡機制),今天這篇文章不打算深入相依管理的細節,有興趣請自行 閱讀文件 。
這篇文章的主軸,是從一個很簡單的需求出發: 我有一個私有的 JAR 檔,希望加入一個由 Apache Maven 管理的 Java 專案中!
因此,你必須先瞭解何謂 Repositories 或 Artifact Repositories !
理解 Artifact Repositories 的概念
在瞭解 Artifact Repositories 之前,我們先來認識幾個抽象的概念與專有名詞。
我在第一次接觸 Apache Maven 的時候,就是學寫 Spring Boot 的時候,透過 Spring Initializr 建立新專案的時候一定要輸入 groupId 、 artifactId 這兩個參數,我問了好幾個人,沒有人可以把這兩個概念解釋的很清楚,就只知道要設定這兩個「參數」而已。這種 知其然而不知所以然 的狀態,會讓我十分困擾。因為我學習任何新事物都一定要「連結」某個既有概念,即便當下對觀念的理解不夠全面,還是要先連結起來輔助我思考,待日後有更深刻的理解時,還可以逐步導正觀念。
以下我就來說說這幾個重要的參數所代表的意義:
groupId這裡的
group有 群組 的意思,也可以理解為一個 團體 ,更可以理解為一個 組織 。而每個 專案 都會隸屬於一個 組織 ,或是 一個組織 可以包含 多個專案 ,這是個「一對多」關係,我想這部分非常容易理解。一般來說這個
groupId會取名成一個組織的 FQDN 域名,例如我們公司的網址是duotify.com,所以公司層級的專案,就會取名為com.duotify當成專案的groupId。如果是公司內部isms團隊的專案,我們的groupId就會取名為com.duotify.isms,以此類推。我們再以
org.apache.maven.plugins這個groupId為例,這個名字所代表的含意是:所有使用這個groupId的專案,全部都歸類在org.apache組織下的maven計畫中的所有plugins專案」artifactId這裡的
artifact有 產出物 的意思,也有 人工製品 的意思,其代表的真實含意是 手工打造的物品 。這個抽象的概念,其實就是我在文章中經常提到的 專案 (Project)。你所親手打造的 專案 ,最終的 產出物 就是Artifact。每個 專案 都會有個名稱,而這個名稱就叫做
artifactId!以 Java 專案而言,無論你是一個 Class Library 或是 Spring Boot 專案,通常專案的最終 產出物 通常是一個 JAR 檔 (
*.jar) 或 WAR 檔 (*.war)。然而,一個專案可能會產生不只一個產出物,因此你還有可能產出含有原始碼的 JAR 檔,其檔名通常也會包含artifactId所標示的名稱。version我們在一個 組織 (
groupId)下,可能會發展多個 專案 (artifactId),而每個專案可能會產出多個 版本 (version),因此groupId+artifactId+version就會自然組成一個全世界唯一的 套件名稱 。通常一個由 Maven 產出的 Artifact 的檔名通常是
<artifactId>-<version>.<extension>這種格式,例如:myapp-1.0.jar
如果我們把焦點拉回這個段落的主軸,什麼是 Artifact Repositories 呢?就是一個可以用來儲存 Artifact (專案產出物) 的儲存庫。
理解 Artifact 與 Project 之間的關係
我們現在知道 Artifact 就是 Project 的產出物,因此我們開發 Spring Boot 的時候,最終產生的 JAR 檔,就是該 Project 的 Artifact 而已。
我們在一個 Spring Boot 專案中所使用的 Dependency (相依套件),說穿了,也就是你在使用其他 Project 所產生的 Artifact (產出物) 而已。
理解了這個概念之後,你就要重新思考一遍,什麼叫做 Dependency (相依套件):
所謂 Dependency 就是你 需要 使用到 另一個專案 的 產出物 來幫助你在這個專案完成任務,但你不用管那個專案的實作細節,也不用管那個專案如何封裝產出物,你只需要很簡單的知道對方的 groupId + artifactId + version 就可以取用其結果,而這就是「抽象化」的過程,你必須理解這個過程,這整件事才不會是「抽象」的,也才能舉一反三、靈活運用。
重新理解 Artifact Repositories 的細節
我們現在知道 Artifact Repositories 就是一個可以用來儲存 Artifact (專案產出物) 的儲存庫,跟 Git Repository 一樣,他也是以檔案的形式來儲存這些內容,他其實就是一個資料庫,除了用來儲存產出物之外,還會保存一些重要的 Metadata,幫助 Maven 取得關鍵資訊。
從 Introduction to Repositories 文件中有提到一段話:
他說在 Maven 之中,一個「儲存庫」會用來保存不同類型的 build artifacts (建置成果) 與 dependencies (相依套件),而且主要有兩種類型:
local(本地儲存庫)儲存在本機的 Maven Repository,通常位於
~/.m2/目錄下,你可以把他視為是一種 遠端儲存庫 的 快取版本 ,因為所有從 遠端儲存庫 下載的artifacts最終都會儲存在 本地儲存庫 之中,也就是存在~/.m2/目錄下。remote(遠端儲存庫)有別於 本地儲存庫 ,所謂的 遠端儲存庫 其實是一個「相對」的概念,他就是一個儲存在「遠端」的儲存庫而已,任何使用
file://或https://開頭的協定,都可以被歸類在「遠端儲存庫」這個類別。所以你可以把所謂的 遠端儲存庫 部署在任何以 HTTP / HTTPS 為主的任意網站上,可以在網際網路上,也可以部署在公司內網。你甚至可以不需要架設 Web 伺服器,直接用 NAS 就可以架設一套公司內部用的 遠端儲存庫 來用,只要使用file://為網址就行。
基本上 local (本地儲存庫) 與 remote (遠端儲存庫) 保存 Artifacts 的目錄結構與格式是完全相同的,大家也可以自由進入 ~/.m2/ 資料夾查看有哪些檔案,其實都是非常公開透明的。
將私有的 JAR 檔加入到 Maven 本地儲存庫
我們假設一下這個情境,我們有個 C:\Program Files\Java\jdk1.6.0_43\jre\lib\plugin.jar 第三方 JAR 檔,由於你的 Java 專案需要在編譯的時候用到該 JAR 檔中的某些類別,你並沒有該檔案的原始碼,但可以理解這個 plugin.jar 其實就是一個 artifact (產出物),而且編譯的時候只要將該檔案加入 -classpath 就可以成功編譯。
此時你想把該 JAR 檔加入到專案中,但是該專案採用 Apache Maven 來管裡所有的相依套件(Dependencies),因此我們必須對該 JAR 檔進行一層抽象化的封裝,以便使用 Maven 的相依管裡機制。
以往,你的專案跟 JAR 檔的相依關係會是緊密耦合的,像這樣:
但是透過 Maven 來管裡相依套件的話,你的專案與 JAR 檔的相依關係會變成以下這樣:
請記得幾個重點:
Maven 的資訊中心就是
pom.xml檔Maven 的相依資訊被紀錄在
pom.xml檔的<dependencies>區段內Maven 的
pom.xml看不到任何*.jar的資訊,因為被Dependencies隔開了 (抽象化),因此打斷了Project與JAR的直接相依關係。
還好 Maven 要把一個既有的 JAR 檔封裝成一個 Maven Repository 的 Artifact 非常容易,只要一個命令就可以完成:
這個命令是直接將 C:\Program Files\Java\jdk1.6.0_43\jre\lib\plugin.jar 封裝成一個 groupId 為 com.duotify 、 artifactId 為 jre6-plugin 與 version 為 1.0.0 且封裝類型為 jar 的 artifact ,並且在不指定 Maven Repository 的情況下,預設就會安裝到 Maven 的本地儲存庫( local repository ),也就是 ~/.m2/ 這個目錄!
當你的 Maven 本地儲存庫已經擁有 jre6-plugin-1.0.0 (com.duotify) 這個相依套件,你就可以在 pom.xml 檔案中加入以下相依設定,就可以自動參考進來,建置專案的時候 Maven 也會自動將該套件的 jre6-plugin-1.0.0.jar 檔加入到 javac 編譯時的 -classpath 之中:
而這,就是 Maven 相依管裡的魅力! :+1:
如我們文章一開始說的,如果客戶目前的開發環境會從他們公司自架的 Maven Repository 下載套件,其實也會安裝到本地的 Maven Repository 才對 ( ~/.m2/ ),所以客戶大可將他自己的 ~/.m2/ 資料夾整個壓縮起來傳給我們,我們只要解壓縮到開發人員本機的 ~/.m2/ 目錄,就可以取得完整的相依套件,就可以在本地開發了。但是你必須用「離線」的方式執行 mvn 命令,加上一個 -o 參數即可。例如:
將私有的 JAR 檔加入到 Maven 專案儲存庫
上述作法只能把 JAR 檔安裝到「本地儲存庫」之中,然而如果你有一整個開發團隊,每個人都需要用到這個 plugin.jar 的話,那麼每個人都需要手動安裝一遍,相當不方便。不過再怎麼不方便,其實一台電腦也只需要跑一次而已,影響不會太大,文件寫清楚也可以。
另一種解決方法,就是直接在「專案」內直接建立好一個資料夾 (假設為 libs 資料夾),並把這個資料夾視為一個「遠端儲存庫」來用,然後你可以將 plugin.jar 這個透過 mvn install:install-file 命令,自動將其加入到我稱做「專案儲存庫」的地方,你只要記得將該目錄加入版控即可。以下是執行命令範例:
這裡的 -DlocalRepositoryPath=libs 就是用來將 artifact 安裝到 libs 專案儲存庫之中!
當你在專案的 libs 建立一個「遠端儲存庫」且已擁有 jre6-plugin-1.0.0 (com.duotify) 這個相依套件,你就可以在 pom.xml 檔案中加入 <repositories> 設定,明確指定遠端 Maven Repository 的位址:
如此一來,就不會有人遇到抓不到 JAR 檔的問題了! :+1:










评论