﻿# 如何將私有的 JAR 檔加入到由 Apache Maven 管理的 Java 專案中

我們最近有個新的 Java 專案，客戶提供原始碼之後，卻發現他們自己架設的 Maven Repository 並沒有開啟防火牆讓我們連線，以致於專案無法下載相依套件而無法建置。但除了開防火牆連線外，其實還有很多方法可以讓你獲取 Maven 所需的相依套件。今天這篇文章，我就來分享關於 Maven 如何管裡相依套件，以及如何正確的將私有的第三方 JAR 檔加到專案內，降低團隊取得這些 JAR 檔的門檻。

![pom.xml](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/d414421354146599f998-189029408-56b68996-d4fc-4efa-8cb0-b5761489e22c.png)

其實我們已經不是只有一次需要將第三方的 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](https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html) (相依管裡機制)，今天這篇文章不打算深入相依管理的細節，有興趣請自行[閱讀文件](https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html)。

這篇文章的主軸，是從一個很簡單的需求出發：**我有一個私有的 JAR 檔，希望加入一個由 Apache Maven 管理的 Java 專案中！**

因此，你必須先瞭解何謂 `Repositories` 或 `Artifact Repositories`！

### 理解 Artifact Repositories 的概念

在瞭解 [Artifact Repositories](https://maven.apache.org/guides/introduction/introduction-to-repositories.html) 之前，我們先來認識幾個抽象的概念與專有名詞。

我在第一次接觸 [Apache Maven](https://maven.apache.org/) 的時候，就是學寫 [Spring Boot](https://spring.io/projects/spring-boot/) 的時候，透過 [Spring Initializr](https://start.spring.io/) 建立新專案的時候一定要輸入 `groupId`、`artifactId` 這兩個參數，我問了好幾個人，沒有人可以把這兩個概念解釋的很清楚，就只知道要設定這兩個「參數」而已。這種[知其然而不知所以然](https://pedia.cloud.edu.tw/Entry/Detail/?title=%E7%9F%A5%E5%85%B6%E7%84%B6%E8%80%8C%E4%B8%8D%E7%9F%A5%E5%85%B6%E6%89%80%E4%BB%A5%E7%84%B6)的狀態，會讓我十分困擾。因為我學習任何新事物都一定要「連結」某個既有概念，即便當下對觀念的理解不夠全面，還是要先連結起來輔助我思考，待日後有更深刻的理解時，還可以逐步導正觀念。

以下我就來說說這幾個重要的參數所代表的意義：

-   `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](https://maven.apache.org/guides/introduction/introduction-to-repositories.html) 文件中有提到一段話：

> A repository in Maven holds build artifacts and dependencies of varying types.

他說在 Maven 之中，一個「儲存庫」會用來保存不同類型的 `build artifacts` (建置成果) 與 `dependencies` (相依套件)，而且主要有兩種類型：

1.  `local` (本地儲存庫)
    
    儲存在本機的 Maven Repository，通常位於 `~/.m2/repository/` 目錄下，你可以把他視為是一種**遠端儲存庫**的**快取版本**，因為所有從**遠端儲存庫**下載的 `artifacts` 最終都會儲存在**本地儲存庫**之中，也就是存在 `~/.m2/repository/` 目錄下。
    
2.  `remote` (遠端儲存庫)
    
    有別於**本地儲存庫**，所謂的**遠端儲存庫**其實是一個「相對」的概念，他就是一個儲存在「遠端」的儲存庫而已，任何使用 `file://` 或 `https://` 開頭的協定，都可以被歸類在「遠端儲存庫」這個類別。所以你可以把所謂的**遠端儲存庫**部署在任何以 HTTP / HTTPS 為主的任意網站上，可以在網際網路上，也可以部署在公司內網。你甚至可以不需要架設 Web 伺服器，直接用 NAS 就可以架設一套公司內部用的**遠端儲存庫**來用，只要使用 `file://` 為網址就行。
    

> 基本上 `local` (本地儲存庫) 與 `remote` (遠端儲存庫) 保存 `Artifacts` 的目錄結構與格式是完全相同的，大家也可以自由進入 `~/.m2/repository/` 資料夾查看有哪些檔案，其實都是非常公開透明的。

### 將私有的 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 檔的相依關係會是緊密耦合的，像這樣：

```
Your Project <---> Artifact (JAR)
```

但是透過 Maven 來管裡相依套件的話，你的專案與 JAR 檔的相依關係會變成以下這樣：

```
Your Project <---> Maven (pom.xml) <---> Dependencies <---> Artifact (JAR)
```

請記得幾個重點：

1.  Maven 的資訊中心就是 `pom.xml` 檔
2.  Maven 的相依資訊被紀錄在 `pom.xml` 檔的 `<dependencies>` 區段內
3.  Maven 的 `pom.xml` 看不到任何 `*.jar` 的資訊，因為被 `Dependencies` 隔開了 (抽象化)，因此打斷了 `Project` 與 `JAR` 的直接相依關係。

還好 Maven 要把一個既有的 JAR 檔封裝成一個 Maven Repository 的 Artifact 非常容易，只要一個命令就可以完成：

```powershell
mvn install:install-file -Dfile="C:\Program Files\Java\jdk1.6.0_43\jre\lib\plugin.jar" "-DgroupId=com.duotify" "-DartifactId=jre6-plugin" "-Dversion=1.0.0" "-Dpackaging=jar" "-DcreateChecksum=true"
```

這個命令是直接將 `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/repository/` 這個目錄！

當你的 Maven 本地儲存庫已經擁有 `jre6-plugin-1.0.0 (com.duotify)` 這個相依套件，你就可以在 `pom.xml` 檔案中加入以下相依設定，就可以自動參考進來，建置專案的時候 Maven 也會自動將該套件的 `jre6-plugin-1.0.0.jar` 檔加入到 `javac` 編譯時的 `-classpath` 之中：

```xml
<dependencies>
    <dependency>
        <groupId>com.duotify</groupId>
        <artifactId>jre6-plugin</artifactId>
        <version>1.0.0</version>
    </dependency>
</dependencies>
```

而這，就是 Maven 相依管裡的魅力！ 👍

如我們文章一開始說的，如果客戶目前的開發環境會從他們公司自架的 Maven Repository 下載套件，其實也會安裝到本地的 Maven Repository 才對 (`~/.m2/repository/`)，所以客戶大可將他自己的 `~/.m2/repository/` 資料夾整個壓縮起來傳給我們，我們只要解壓縮到開發人員本機的 `~/.m2/repository/` 目錄，就可以取得完整的相依套件，就可以在本地開發了。但是你必須用「離線」的方式執行 `mvn` 命令，加上一個 `-o` 參數即可。例如：

```bash
mvn -o package
```

### 將私有的 JAR 檔加入到 Maven 專案儲存庫

上述作法只能把 JAR 檔安裝到「本地儲存庫」之中，然而如果你有一整個開發團隊，每個人都需要用到這個 `plugin.jar` 的話，那麼每個人都需要手動安裝一遍，相當不方便。不過再怎麼不方便，其實一台電腦也只需要跑一次而已，影響不會太大，文件寫清楚也可以。

另一種解決方法，就是直接在「專案」內直接建立好一個資料夾 (假設為 `libs` 資料夾)，並把這個資料夾視為一個「遠端儲存庫」來用，然後你可以將 `plugin.jar` 這個透過 `mvn install:install-file` 命令，自動將其加入到我稱做「專案儲存庫」的地方，你只要記得將該目錄加入版控即可。以下是執行命令範例：

```powershell
mvn install:install-file -Dfile="C:\Program Files\Java\jdk1.6.0_43\jre\lib\plugin.jar" "-DgroupId=com.duotify" "-DartifactId=jre6-plugin" "-Dversion=1.0.0" "-Dpackaging=jar" "-DcreateChecksum=true" "-DlocalRepositoryPath=libs"
```

> 這裡的 `-DlocalRepositoryPath=libs` 就是用來將 `artifact` 安裝到 `libs` 專案儲存庫之中！

當你在專案的 `libs` 建立一個「遠端儲存庫」且已擁有 `jre6-plugin-1.0.0 (com.duotify)` 這個相依套件，你就可以在 `pom.xml` 檔案中加入 `<repositories>` 設定，明確指定遠端 Maven Repository 的位址：

```xml
<repositories>
    <repository>
        <id>ProjectRepo</id>
        <name>ProjectRepo</name>
        <url>file:///${project.basedir}/libs</url>
    </repository>
</repositories>

<dependencies>
    <dependency>
        <groupId>com.duotify</groupId>
        <artifactId>jre6-plugin</artifactId>
        <version>1.0.0</version>
    </dependency>
</dependencies>
```

如此一來，就不會有人遇到抓不到 JAR 檔的問題了！ 👍

### 相關連結

-   [Maven - Introduction to Repositories](https://maven.apache.org/guides/introduction/introduction-to-repositories.html)
-   [Maven - Guide to installing 3rd party JARs](https://maven.apache.org/guides/mini/guide-3rd-party-jars-local.html)
-   [Maven - Introduction to the Dependency Mechanism](https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html)
-   [java - How to add additional .jar to JRE System Library \[JavaSE-1.6\] in VSCode? - Stack Overflow](https://stackoverflow.com/a/73617726/910074)
-   [java - Can I add jars to Maven 2 build classpath without installing them? - Stack Overflow](https://stackoverflow.com/a/364188/910074)
-   [java - How to include system dependencies in war built using maven - Stack Overflow](https://stackoverflow.com/a/19066828/910074)
-   [Maven: add a folder or jar file into current classpath - Stack Overflow](https://stackoverflow.com/a/3410712/910074)
-   [聊聊軟體交付的濫觴 談產出物管理 (Artifacts Management) | Complete Think](https://rickhw.github.io/2019/03/28/DevOps/DevOpsTaiwan-Meetup-Beginning-in-Artifacts-Management/)
