maven实战总结,工作中常见操作
點擊上方?好好學java?,選擇?星標?公眾號
重磅資訊、干貨,第一時間送達
今日推薦:硬剛一周,3W字總結,一年的經驗告訴你如何準備校招!
個人原創100W+訪問量博客:點擊前往,查看更多
思維導圖
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?暫時不演示坐標與依賴
什么是坐標
類比為數學中平面幾何,坐標(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 依賴。
倉庫
倉庫分類:包括本地倉庫和遠程倉庫。其中遠程倉庫包括:私服和中央倉庫。搜索構建的順序:
本地倉庫
maven settings profile 中的 repository;
pom.xml 中 profile 中定義的repository;
pom.xml 中 repositorys (按定義順序找);
maven settings mirror;
central 中央倉庫;
生命周期
Maven 的生命周期是為了對所有構建過程進行的抽象和統一,其中包含項目的清理、初始化、編譯、測試、打包、集成測試、驗證、部署和站點生成等幾乎所有的構建步驟。
Maven 的生命周期是抽象的,本身是不做任何實際的工作。實際的任務都交給插件來完成。
意味著 Maven 只在父類中定義了算法的整體結構,子類通過重寫父類的方法,來控制實際行為(設計模式中的模板方法 Template Method)。偽代碼如下:
public?abstract?class?AbstractBuilder?{public?void?build()?{init();compile();test();package();integrationTest();deploy();}protected?abstract?void?init();protected?abstract?void?compile();protected?abstract?void?test();protected?abstract?void?package();protected?abstract?void?integrationTest();protected?abstract?void?deploy(); }三套生命周期
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 | 將生成的項目站點發布到服務器上。 |
插件
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>
聚合與繼承
聚合:為了一次構建多個項目模塊,就需要對多個項目模塊進行聚合
<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 標簽。
靈活構建
使用屬性、 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/testclasses/.
${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>源于:https://juejin.im/post/6844903839435341832
推薦文章
硬剛一周,3W字總結,一年的經驗告訴你如何準備校招!
今年的校招,Java 好拿 offer 嗎?
10月了,該聊聊今年秋招了!
聊聊在騰訊實習快一個月的感受
總結
以上是生活随笔為你收集整理的maven实战总结,工作中常见操作的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 跟前腾讯总监学Java实战项目
- 下一篇: IDEA 上位?不!Eclipse Th