當前位置: 妍妍網 > 碼農

maven 實戰總結,工作中常見操作!

2024-06-27碼農

點選「 IT碼徒 」, 關註,置頂 公眾號

每日技術幹貨,第一時間送達!

1

思維導圖

2

Maven 與構建

什麽是 Maven

轉譯:知識的積累、專家、內行。跨平台的計畫管理工具。Apache 組織的開源計畫。主要服務於基於 Java 平台的計畫構建、依賴管理和計畫資訊管理。

類似於 linux 平台的 yum、apt,前端領域的 npm。Maven 前身為 Ant 目前 tomcat 的源碼就是用 Ant 來構建和管理,更先進的工具有 Gradle, Spring 工程在用。

什麽是構建

何為構建:編譯、執行單元測試、生成文件、打包、部署的過程,這就是構建。

構建的步驟:

  • 清理 clean:將以前編譯得到的舊檔 class 字節碼檔刪除。

  • 編譯 compile:將 java 源程式編譯成 class 字節碼檔。

  • 測試 test:自動測試,自動呼叫 junit 程式。

  • 報告 report:測試程式執行的結果。

  • 打包 package:動態 Web 工程打 War 包,java 工程打 jar 包。

  • 安裝 install:將打包得到的檔復制到 「倉庫」 中的指定位置(Maven特定的概念)。

  • 部署 deploy:將動態 Web 工程生成的 war 包復制到 Servlet 容器下,使其可以執行。

  • 計畫骨架

    pom:Project Object Model

    根目錄:工程名
    |---src:源碼
    |
    ---|---main:主程式
    |
    ---|---|---java:主程式程式碼路徑
    |---|---|---resource:主程式配置檔路徑
    |
    ---|---test:測試
    |
    ---|---|---java:測試程式碼路徑
    |---|---|---resource:測試配置檔路徑
    |
    ---pom.xml:maven 配置檔

    簡單演示

    ## 1. 使用 archetype 命令生成 maven 簡單骨架
    mvn archetype:generate -DarchetypeCatalog=internal
    #
    # 2. 編譯當前生成的計畫
    mvn compile

    #
    # 3. 使用其他命令
    mvn test-compile
    mvn package
    mvn clean
    mvn install
    mvn depoly 暫時不演示

    3

    座標與依賴

    什麽是座標

    類比為數學中平面幾何,座標(x、y ),任何一個座標都能唯一標識該平面中的一個點。

    該點對應到 maven 就是 .jar、.war 等檔的檔。

    Maven 使用 groupId artifactId version packaging classifier 等元素來組成自己的座標,並定義一組這樣的規則,只要能提供正確座標元素 Maven 就能找到對應的構件。

    座標元素

  • groupId:定義當前 Maven 計畫隸屬的實際計畫。

  • artifactId:定義實際計畫中的一個 Maven 計畫(模組)。

  • packaging:定義 Maven 計畫打包方式。jar、war、pom。預設為 jar。

  • version:定義 Maven 計畫當前所處的版本。

  • classifier:區分從同一 artifact 構建的具有不同內容的構件。

  • classifier 使用場景

    區分基於不同 JDK 版本的包

    <dependency>
    <groupId>net.sf.json-lib</groupId>
    <artifactId>json-lib</artifactId>
    <version>2.2.2</version>
    < classifier>jdk13</ classifier>
    <!--< classifier>jdk15</ classifier>-->
    </dependency>

    區分計畫的不同組成部份

    <dependency>
    <groupId>net.sf.json-lib</groupId>
    <artifactId>json-lib</artifactId>
    <version>2.2.2</version>
    < classifier>jdk15-javadoc</ classifier>
    <!--< classifier>jdk15-sources</ classifier> -->
    </dependency>

    構件名與座標是對應的,一般規則是: artifactId-version[- classifier].packaging

    依賴聲明

    <dependencies>
    <dependency>
    <groupId></groupId>
    <artifactId></artifactId>
    <version></version>
    <type></type>
    <optional></optional>
    <exclusions>
    <exclusion>
    <artifactId></artifactId>
    <groupId></groupId>
    </exclusion>
    ...
    </exclusions>
    </dependency>
    ...
    </dependencies>

  • groupId、artifactId、version:依賴的基本座標。

  • type:依賴的型別,對應計畫對應的 packaging,一般不必聲明。

  • scope:依賴的範圍,後面詳解。

  • optional:標記依賴是否可選。

  • exclusions:用來排除傳遞性依賴。

  • 依賴範圍

  • compile:編譯依賴範圍

  • 如果沒有指定,預設使用該依賴範圍。對於編譯、測試、執行三種 classpath 都有效。如:spring-core。

  • test:測試依賴範圍

  • 只對於測試 classpath 有效,只需要在編譯測試及執行測試才需要,在打包的時候不會打進去。如:JUnit。

  • provided:已提供依賴範圍

  • 對於編譯和測試 classpath 有效,但執行時無效。如:servlet-api 編譯和測試計畫的時候都需要,但在實際執行中,容器已經提供,不需要 maven 重復的參照。

  • runtime:執行時依賴範圍

  • 對於測試和執行的 classpath 有效,但在編譯主程式碼時無效。如:JDBC 驅動的實作包。只有在執行測試或者執行計畫時,才需要具體的 JDBC 驅動。

  • system:系統依賴範圍

  • 與 provided 依賴範圍完全一致,但是使用該範圍時必須透過 systemPath 元素顯式地指定依賴檔的路徑。由於此類依賴不是透過 maven 倉庫解析的,而且往往與本機系統繫結,可能造成構建不可移植,因此應該謹慎使用。systemPath 元素可以參照環境變量,如:

    <dependencies>
    <dependency>
    <groupId>javax.sql</groupId>
    <artifactId>jdbc-stdxt</artifactId>
    <version>2.0</version>
    <scope>system</scope>
    <systemPath>${java.home}/lib/rt.jar</systemPath>
    </dependency>
    </dependencies>

  • import:匯入依賴範圍

  • 只在 dependencyManagement 標簽中生效,匯入已經定義好的 pom 檔中 dependencyManagement 節點內容

    <dependencyManagement>
    <dependencies>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-framework-bom</artifactId>
    <version>4.3.16.RELEASE</version>
    <type>pom</type>
    <scope>import</scope>
    </dependency>
    </dependencies>
    </dependencyManagement>

    依賴機制與特性

    依賴傳遞

  • A->B(compile):第一直接依賴

  • B->C(compile):第二直接依賴

  • A->C(compile):傳遞性依賴

  • 當在A中配置

    <dependency>
    <groupId>com.B</groupId>
    <artifactId>B</artifactId>
    <version>1.0</version>
    </dependency>

    則會自動匯入 C 包。

    傳遞性依賴的範圍如下圖所示:

    依賴調解

    當傳遞性依賴出現問題時,能夠清楚地知道該傳遞性依賴是從哪條依賴路徑中引入的。

    一、路徑最近者優先原則

  • A->B->C->X(1.0)

  • A->D->X(2.0)

  • 由於只能匯入一個版本的包,按照最短路徑選擇匯入 X(2.0)

    二、第一聲明者優先原則

  • A->B->Y(1.0)

  • A->C->Y(2.0)

  • 此時由於依賴路徑長度一致,按照第一聲明者優先原則。在路徑長度一致的前提下,如果 B 依賴在 POM 檔中聲明順序在 C 依賴之前,那麽 Y(1.0) 則會被引入。如下依賴可用於測試:

    <dependencies>
    <dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.4.1</version>
    <exclusions>
    <exclusion>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    </exclusion>
    </exclusions>
    </dependency>
    <dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>3.9</version>
    <exclusions>
    <exclusion>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    </exclusion>
    </exclusions>
    </dependency>
    <dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.10</version>
    </dependency>
    </dependencies>

    這裏有一點需要特別註意,看如下依賴:

    <dependencies>
    <dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.11</version>
    </dependency>
    <dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.10</version>
    </dependency>
    </dependencies>

    按照兩原則,期望得到的結果應該是 1.11 版本的構建將被依賴。但實際結果卻依賴了 1.10 版本。what!這不是違反了 maven 依賴調解的最先定義原則?

    其實這個是 dependency 外掛程式的功能,預設采用的是復寫的策略,當構建聲明處於同一 pom 中,且 groupid 和 artifactId 一致時,以最新聲明為準,後面的覆蓋前面的。

    註意這裏沒涉及到依賴調解的功能。我的理解是依賴調解只發生於構建來自不同 pom 時,而此時構建聲明處於同一 pom,故不會觸發依賴調解。

    可選依賴

    A->B、B->X(可選)、B->Y(可選)。

    計畫 A 依賴於計畫 B,計畫 B 依賴於計畫 X 和 Y。

    理論上計畫 A 中,會把 B、X、Y 計畫都依賴進來。

    但是 X、Y 兩個依賴對於 B 來講可能是互斥的,如 B 是資料庫隔離包,支持多種資料庫 MySQL、Oracle,在構建 B 計畫時,需要這兩種資料庫的支持,但在使用這個工具包時,只會依賴一個資料庫。

    此時就需要在 B 計畫 pom 檔中將 X、Y 聲明為可選依賴,如下:

    <dependency>
    <groupId>com.X</groupId>
    <artifactId>X</artifactId>
    <version>1.0</version>
    <optionnal>true</optionnal>
    </dependency>
    <dependency>
    <groupId>com.Y</groupId>
    <artifactId>Y</artifactId>
    <version>1.0</version>
    <optionnal>true</optionnal>
    </dependency>

    使用 optionnal 元素標識以後,只會對當前計畫 B 產生影響,當其他的計畫依賴 B 計畫時,這兩個依賴都不會被傳遞。

    計畫 A 依賴於計畫 B,如果實際套用資料庫是 X, 則在 A 的 pom 中就需要顯式地聲明 X 依賴。

    4

    倉庫

    倉庫分類:包括本地倉庫和遠端倉庫。其中遠端倉庫包括:私服和中央倉庫。搜尋構建的順序:

  • 本地倉庫

  • maven settings profile 中的 repository;

  • pom.xml 中 profile 中定義的repository;

  • pom.xml 中 repositorys (按定義順序找);

  • maven settings mirror;

  • central 中央倉庫;

  • 5

    生命周期

    Maven 的生命周期是為了對所有構建過程進行的抽象和統一,其中包含計畫的清理、初始化、編譯、測試、打包、整合測試、驗證、部署和站點生成等幾乎所有的構建步驟。

    Maven 的生命周期是抽象的,本身是不做任何實際的工作。實際的任務都交給外掛程式來完成。

    意味著 Maven 只在父類中定義了演算法的整體結構,子類別透過重寫父類的方法,來控制實際行為(設計模式中的樣版方法 Template Method)。虛擬碼如下:

    publicabstract classAbstractBuilder{
    publicvoidbuild(){
    init();
    compile();
    test();
    package();
    integrationTest();
    deploy();
    }
    protectedabstractvoidinit();
    protectedabstractvoidcompile();
    protectedabstractvoidtest();
    protectedabstractvoidpackage();
    protectedabstractvoidintegrationTest();
    protectedabstractvoiddeploy();
    }

    三套生命周期

    Maven 的生命周期並不是一個整體,Maven 擁有三套相互獨立的生命周期,它們分別為 clean、default 和 site。

  • clean 生命周期的目的是清理計畫;

  • default 生命周期的目的是構建計畫;

  • site 生命周期的目的是建立計畫站點;

  • 單個生命周期執行順序

    每個生命周期包含一些階段(phase),這些階段是有順序的,並且後面的階段依賴於前面的階段。

    以 clean 生命周期為例,它包含的階段有 pre-clean、clean和post-clean。當呼叫 pre-clean 時,只有 pre-clean 階段得以執行;

    當呼叫 clean 的時候,pre-clean和clean階段會得以順序執行,以此類推。

    各個生命周期之間的關系

    三套生命周期本身是相互獨立的,使用者可以僅呼叫 clean 生命周期的某個階段,或者僅僅呼叫 default 生命周期的某個階段,而不會對其他生命周期產生任何影響。

    例如,當使用者呼叫 clean 生命周期的 clean 階段的時候,不會觸發 default 生命周期的任何階段,反之亦然。

    生命周期各個階段詳解

    clean

    生命周期階段

    描述

    pre-clean

    執行一些清理前需要完成的工作。

    clean

    清理上一次構建生成的檔。

    post-clean

    執行一些清理後需要完成的工作。

    default

    包含 23 個階段,此處只介紹重點步驟,如下表:

    生命周期階段

    描述

    validate

    檢查工程配置是否正確,完成構建過程的所有必要資訊是否能夠獲取到。

    initialize

    初始化構建狀態,例如設定內容。

    generate-sources

    process-sources

    處理計畫資原始檔,處理計畫主資原始檔。一般來說,是對src/main/resources目錄的內容進行變量替換等工作後,復制到計畫輸出的主 classpath目錄中。

    generate-resources

    process-resources

    compile

    編譯計畫的主源碼。一般來說,是編譯src/main/java目錄下的Java檔至計畫輸出的主 classpath目錄中。

    process- classes

    處理編譯生成的檔,例如 Java class 字節碼的加強和最佳化。

    generate-test-sources

    process-test-sources

    處理計畫測試資原始檔。一般來說,是對src/test/resources目錄的內容進行變量替換等工作後,復制到計畫輸出的測試 classpath目錄中。

    test-compile

    編譯計畫的測試程式碼。一般來說,是編譯src/test/java目錄下的Java檔至計畫輸出的測試 classpath目錄中。

    process-test- classes

    test

    使用適當的單元測試框架(例如JUnit)執行測試。

    prepare-package

    在真正打包之前,為準備打包執行任何必要的操作。

    package

    獲取編譯後的程式碼,並按照可釋出的格式進行打包,例如 JAR、WAR 或者 EAR 檔。

    pre-integration-test

    在整合測試執行之前,執行所需的操作。例如,設定所需的環境變量。

    integration-test

    處理和部署必須的工程包到整合測試能夠執行的環境中。

    post-integration-test

    在整合測試被執行後執行必要的操作。例如,清理環境。

    verify

    執行檢查操作來驗證工程包是有效的,並滿足品質要求。

    install

    安裝工程包到本地倉庫中,該倉庫可以作為本地其他工程的依賴。

    deploy

    拷貝最終的工程包到遠端倉庫中,以共享給其他開發人員和工程。


    site

    生命周期階段

    描述

    pre-site

    執行一些在生成計畫站點之前需要完成的工作。

    site

    生成計畫站點文件。

    post-site

    執行一些在生成計畫站點之後需要完成的工作。

    site-deploy

    將生成的計畫站點釋出到伺服器上。

    5

    外掛程式

    Maven 三套生命周期定義各個階段不做任何實際工作,實際工作都是由外掛程式來完成的,每個生命周期階段都是由外掛程式的目標來完成。在 pom 檔中聲明如下(打包源碼檔外掛程式):

    <build>
    <plugins>
    <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-source-plugin</artifactId>
    <version>2.1.1</version>
    <executions>
    <execution>
    <id>attach-sources</id>
    <phase>verify</phase>
    <goals>
    <goal>jar-no-fork</goal>
    </goals>
    </execution>
    </executions>
    </plugin>
    </plugins>
    </build>

    外掛程式目標

    一個外掛程式有可能有多個功能、每個功能就是一個目標。比如 maven-dependency-plugin 有十多個目標,每個目標對應了一個功能。

    外掛程式的目標為 dependency:analyze、dependency:tree和dependency:list。

    通用寫法:冒號前面是外掛程式字首,冒號後面是外掛程式的目標。比如 compiler:compile。

    外掛程式繫結

    內建繫結

    為實作快速構建,Maven 有一套內建的外掛程式繫結。三套生命周期的外掛程式繫結具體如下(其實是各個生命周期階段與外掛程式的目標的繫結)。

    其中 default 生命周期的構建方式會其打包型別有關、打包型別在POM中 packaging 指定。一般有 jar、war 兩種型別。下面是預設繫結外掛程式與生命周期關系圖:

    自訂繫結

    自訂繫結允許我們自己掌控外掛程式目標與生命周期的結合。以生成計畫主程式碼的源碼 jar 為例。

    使用到的外掛程式和它的目標為:maven-source-plugin:jar-no-fork。將其繫結到 default 生命周期階段 verify 上(可以任意指定三套生命周期的任意階段)。

    <build>
    <plugins>
    <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-source-plugin</artifactId>
    <version>2.1.1</version>
    <executions>
    <execution>
    <id>attach-sources</id>
    <!-- 指定作用在生命周期的哪個階段 -->
    <phase>verify</phase>
    <goals>
    <!-- 指定執行繫結外掛程式的哪些目標 -->
    <goal>jar-no-fork</goal>
    </goals>
    </execution>
    </executions>
    </plugin>
    </plugins>
    </build>

    外掛程式配置

  • 使用命令列配置

  • 在 maven 命令中加入 -D 參數,並伴隨一個參數鍵=參數值的形式,來配置外掛程式目標參數。

    如:maven-surefire-plugin 外掛程式提供一個 maven.test.skip 參數,當值為 true 時會跳過執行測試:

    -- 對比 mvn install
    mvn install –Dmaven.test.skip=true

  • 使用 pom 全域配置

  • 在聲明外掛程式的時候,對外掛程式進行一個全域配置,後面所有使用該外掛程式的都要遵循這個配置。比如指定 maven-compile-plugin 編譯 1.7 版本的原始檔:

    <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
    <fork>true</fork>
    <source>1.7</source>
    <target>1.7</target>
    </configuration>
    </plugin>

    6

    聚合與繼承

    聚合: 為了一次構建多個計畫模組,就需要對多個計畫模組進行聚合

    <modules>
    <module>模組一</module>
    <module>模組二</module>
    <module>模組三</module>
    </modules>

    繼承: 為了消除重復,把很多相同的配置提取出來,例如:dependency、grouptId,version 等

    <parent>
    <groupId>com.xxxx.maven</groupId>
    <artifactId>parent-project</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <relativePath>../ParentProject/pom.xml</relativePath>
    </parent>

    以下的元素是可以被繼承的:

  • groupId,計畫組ID;

  • version,計畫版本;

  • description,計畫描述資訊;

  • organazation,計畫的組織資訊;

  • inceptionYear,計畫的創始年份;

  • developers,計畫開發者資訊;

  • contributors,計畫的貢獻者資訊;

  • distributionManagement,計畫的部署資訊;

  • issueManagement,計畫的缺陷跟蹤系統資訊;

  • ciManagement,計畫的持續整合系統資訊;

  • scm,計畫的版本控制系統資訊;

  • mailingLists,計畫的信件列表資訊;

  • properties,自訂的Maven內容;

  • dependencies,計畫的依賴配置;

  • dependencyManagement,計畫的依賴管理配置;

  • repositories,計畫的倉庫配置;

  • build,包括計畫的源碼目錄配置、輸出目錄配置、外掛程式配置、外掛程式管理配置等;

  • reporting,包括計畫的報告輸出目錄配置、報告外掛程式配置。

  • 註意下面的元素,這些都是不能被繼承的:

  • artifactId

  • name

  • prerequisites

  • 聚合與繼承之間的關系

  • 兩者共同點為,打方式必須都是 pom

  • 在實際的計畫中,一個 pom 既是聚合 pom 又是父 pom

  • 註:父 pom 中使用 dependencies 引入的依賴也會被子 pom 繼承,所以不要將過多的實際依賴放在父 pom,父 pom 只用於管理,使用 dependencyManagement 標簽。

    7

    靈活構建

    使用內容、 resources 外掛程式資源過濾功能(filter)和 Maven 的 profile 功能,實作環境的靈活切換

    內容

    透過 properties 元素使用者可以自訂一個或者多個 Maven 內容,然後在 pom 其他的地方使用 ${內容名} 的方式參照該內容,這種方式最大意義在於消除重復。

    一、內建內容

  • ${basedir} 表示計畫根目錄,即包含 pom.xml 檔的目錄

  • ${version} 等同於或者 {pom.version} 表示計畫版本

  • 二、POM 內容

    所有 pom 中的元素都可以用 project. 例如 ${project.artifactId} 對應了 < project>元素的值。常用的 POM 內容包括:

  • ${project.build.sourceDirectory} : 計畫的主源碼目錄,預設為 src/main/java/.

  • ${project.build.testSourceDirectory} : 計畫的測試源碼目錄,預設為 /src/test/java/.

  • ${project.build.directory} : 計畫構建輸出目錄,預設為 target/.

  • ${project.build.outputDirectory} : 計畫主程式碼編譯輸出目錄,預設為 target/ classes/.

  • ${project.build.testOutputDirectory} : 計畫測試程式碼編譯輸出目錄,預設為 target/test classes/.

  • ${project.groupId}: 計畫的 groupId.

  • ${project.artifactId} : 計畫的 artifactId.

  • ${project.version} : 計畫的 version, 等同於 ${version}

  • ${project.build.finalName} : 計畫打包輸出檔的名稱,預設為 ${project.artifactId}${project.version}

  • 三、自訂內容

    在 pom 中元素下自訂的 Maven 內容

    <properties>
    <swagger.version>2.2.2</swagger.version>
    </properties>
    <dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>${swagger.version}</version>
    </dependency>

    四、Settings 內容

    所有用的的 settings.xml 中的設定都可以透過 settings。字首進行參照與 POM 內容同理。

    如 ${settings.localRepository} 指向使用者本地倉庫的地址

    五、Java 系統內容

    所有 Java 系統內容都可以使用 Maven 內容參照,例如 ${user.home} 指向了使用者目錄。

    可以透過命令列 mvn help:system 檢視所有的 Java 系統內容

    六、環境變量內容

    所有環境變量都可以使用以 env. 開頭的 Maven 內容參照。例如 ${env.JAVA_HOME} 指代了 JAVA_HOME 環境變量的值。

    也可以透過命令列 mvn help:system 檢視所有環境變量。

    七、父級工程內容

    上級工程的 pom 中的變量用字首 {parent.version}

    Profile

    profile 特性可以讓我們定義多個 profile,然後每個 profile 對應不同的啟用條件和配置資訊,從而達到不同環境使用不同配置資訊的效果。

    profile 可以在以下幾個地方聲明:

  • m.xml:這裏聲明的 profile 只對當前計畫有效

  • 使用者 settings.xml:.m2/settings.xml 中的 profile 對該使用者的 Maven 計畫有效

  • 全域 settings.xml:conf/settings.xml,對本機上所有 Maven 計畫有效

  • 範例:

    <project>
    ...
    <profiles>
    <profile>
    <id>dev</id>
    <properties>
    <active.profile>dev</active.profile>
    <key1>value1</key1>
    <key2>value2</key2>
    </properties>
    <!-- 預設啟用配置 -->
    <activation>
    <activeByDefault>true</activeByDefault>
    </activation>
    <!-- 在該 profile 下才會引入的依賴 -->
    <dependencies>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>3.2.4.RELEASE</version>
    </dependency>
    <dependencies>
    <!-- 在該 profile 下才會載入的變量檔 -->
    <build>
    <filters>
    <filter>../profile/test-pre.properties</filter>
    </filters>
    </build>
    </profile>
    </profiles>
    ...
    </project>

    END

    PS:防止找不到本篇文章,可以收藏點贊,方便翻閱尋找哦。

    往期推薦