每一個程序員都該學會的Maven知識,你都能運用這些嗎?

之前的日子

之前咱們寫代碼時,jar包都默認放在一個叫 /lib 的目錄下,而後把該目錄設置爲classpath能夠讀取到的目錄,以下圖所示:java

每一個程序員都該學會的Maven知識,你都能運用這些嗎?

某一天咱們新加了一個功能,須要用到一個比較古老的 z.jar 包,這時咱們到網上去各類搜索,因爲比較罕見,最終在某個 xxx軟件園 中找到了他。而後咱們把 z.jar 包拷貝到 /lib 目錄下:mysql

每一個程序員都該學會的Maven知識,你都能運用這些嗎?

這時運行後報了一堆的錯,緣由是 z.jar 包有不少的依賴項,分別是 z1.jar ,z2.jar , z3.jar。這時的你是否有種想要罵人的衝動?可是衝動歸衝動,代碼仍是要寫,jar包仍是要找,本身挖的坑,哭着也要填完啊。程序員

歷經千難萬險,終於在1個博客中找到了 z1.jar , z2.jar , z3.jar 的下載連接,可是須要支付積分才能下載。。爲了獲得這些jar包,咱們又註冊了一個帳號,發了幾個評論,賺到足夠的積分後,終於把那三個jar包下載下來了。而後趕忙把他們拷貝到 /lib 下去:web

每一個程序員都該學會的Maven知識,你都能運用這些嗎?

湊夠了jar包以後,再次運行項目,終於能成功運行了,真是謝天謝地!spring

過了半個月,老闆說咱們 項目A 很是不錯,如今咱們準備再啓動一個 項目B 做爲他的兄弟項目。這時你開始搭建 項目B 的框架了,把全部須要用到的jar包從 項目A 拷貝到 項目B 中:sql

每一個程序員都該學會的Maven知識,你都能運用這些嗎?

今後你又開始了打怪升級的日子了。apache

如今的日子

隨着科技的發展,改革開放的浪潮席捲了大地,也席捲了IT界,一大堆生產力工具被創造出來了,俗話說的好,工欲善其事必先利其器。有了好的生產力工具,要作的事一定是事半功倍!而 Maven 就是一種爲了解放咱們程序猿的生產力工具。api

程序猿在平常工做中須要用到大量的jar包,有的是框架包如:netty,sentinel等,有的是工具包如:hutool,有的是公司內部的私有包如:xx-framework等等。tomcat

一個項目中可能充斥着各類各樣的jar包,若是咱們用手工的方式去一個一個的管理的話,那樣就會迷失在jar包的海洋裏,這時咱們經過 Maven 這種管理jar包的工具來幫助咱們解決這個繁瑣又棘手的問題,可讓咱們專心於本身的功能與業務。安全

其實 Maven 是一套軟件工程管理和整合工具。他有不少的功能包括但不限於如下幾點:

  • 工程的建立、構建、測試
  • 依賴的管理
  • 倉庫的管理
  • 自動化部署
  • 。。。

咱們平常中用的最多的可能就是工程與依賴的管理了,其餘的用到的頻率很少。

有了 Maven 以後,咱們:

  • 不須要爲每一個項目都建立一個 /lib 目錄用來存放各類jar包了
  • 不須要爲到哪去尋找我須要的jar包而發愁了
  • 不須要爲引用的jar包去尋找他所依賴的jar包了
  • 。。。

結構

下面是一個典型的maven項目的結構圖:

每一個程序員都該學會的Maven知識,你都能運用這些嗎?

倉庫

在 Maven 的術語中,倉庫是一個位置(place),例如目錄,能夠存儲全部的工程 jar 文件、library jar 文 件、插件或任何其餘的工程指定的文件。

嚴格意義上說,Maven 只有兩種類型的倉庫:

  • 本地(local)
  • 遠程(remote)

本地倉庫

Maven 的本地倉庫是機器上的一個文件夾。它在你第一次運行任何 maven 命令的時候建立。

Maven 的本地倉庫保存你的工程的全部依賴(library jar、plugin jar 等)。當你運行一次 Maven 構建時,Maven 會自動下載全部依賴的 jar 文件到本地倉庫中。它避免了每次構建時都引用存放在遠程倉庫上的依賴文件。

Maven 的本地倉庫默認被建立在 ${user.home}/.m2/repository 目錄下。要修改默認位置,只要在 settings.xml 文件中定義另外一個路徑便可,例如:

<localRepository>/anotherDirectory/.m2/respository</localRepository>

遠程倉庫

Maven 的遠程倉庫能夠是任何其餘類型的存儲庫,可經過各類協議,例如 file://http:// 來訪問。

這些存儲庫能夠是由第三方提供的可供下載的遠程倉庫,例如Maven 的中央倉庫(central repository):

repo.maven.apache.org/maven2

uk.maven.org/maven2

也能夠是在公司內的FTP服務器或HTTP服務器上設置的內部存儲庫,用於在開發團隊和發佈之間共享私有的 artifacts。

中央倉庫

Maven 的中央倉庫是 Maven 社區維護的,裏面包含了大量經常使用的庫,咱們能夠直接引用,可是前提是咱們的項目可以訪問外網。

私有倉庫

除了 Maven 的中央倉庫外,還有一種就是私有倉庫,這種倉庫一般都是企業內部建立的一個私有庫,用於一些內部jar包的維護與共享。因爲網絡緣由和鑑於安全性的考慮,不少公司的內外網是隔離的,要想直接訪問中央倉庫是不可能的,而且直接把內部資源暴露在互聯網上也是很是危險的,因此這時就須要建立一個私有庫。

那這些倉庫之間的關係是怎樣的呢?或者說一個 Maven 項目想要獲取一個jar包的話,他該從哪一個倉庫中去獲取呢?下圖就是一個簡單的描述:

每一個程序員都該學會的Maven知識,你都能運用這些嗎?

首先 Maven 會到本地倉庫中去尋找所須要的jar吧,若是找不到就會到配置的私有倉庫中去找,若是私有倉庫中也找不到的話,就會到配置的中央倉庫中去找,若是仍是找不到就會報錯。可是這中間只要在某一個倉庫中找到了就會返回了,除非倉庫中有更新的版本,或者是snapshot版本。

那麼 Maven 的遠程倉庫是怎麼配置的呢?假設咱們要配置一箇中央倉庫,能夠像下面這樣配置:

<project>
...
    <profiles>
        <profile>
              <id>central</id>  
            <repositories>
                <repository>
                    <id>Central</id>
                    <name>Central</name>
                    <url>http://repo.maven.apache.org/maven2/</url>
                </repository>
            </repositories>
        </profile>
    </profiles>
    <activeProfiles>
        <activeProfile>central</activeProfile>
    </activeProfiles>
...
</project>

最佳實踐

可是官方並不推薦直接配置遠程倉庫,例如直接配置一箇中央倉庫,而是經過 倉庫管理器 來下載咱們所須要的jar包。試想一下若是你所在的公司有幾千甚至上萬的開發者,每一個人都單獨配置一箇中央倉庫,那每一個人都到中央倉庫中去下載所需的jar,這就退化成最原始的模式,而且是一個巨大的資源浪費。

那什麼是 倉庫管理器 呢?倉庫管理器是一種專用服務器應用程序,目的是用來管理二進制組件的存儲庫。對於任何使用 Maven 的項目,倉庫管理器的使用被認爲是必不可少的最佳實踐。

倉庫管理器提供瞭如下基本用途:

  • 充當中央Maven存儲庫的專用代理服務器
  • 提供存儲庫做爲Maven項目輸出的部署目標

使用倉庫管理器能夠得到如下優勢和功能:

  • 顯著減小了遠程存儲庫的下載次數,節省了時間和帶寬,從而提升了構建性能
  • 因爲減小了對外部存儲庫的依賴,提升了構建穩定性
  • 與遠程SNAPSHOT存儲庫交互的性能提升
  • 提供了一個有效的平臺,用於在組織內外交換二進制工件,而無需從源代碼中構建工件
  • 。。。

已知的開源和商業存儲庫管理器有如下這些:

  • Apache Archiva(開源)
  • CloudRepo(商業)
  • Cloudsmith套餐(商業)
  • JFrog Artifactory開源(開源)
  • JFrog Artifactory Pro(商業)
  • Sonatype Nexus OSS(開源)
  • Sonatype Nexus Pro(商業)
  • packagecloud.io(商業)

鏡像

Mirror 則至關於一個代理,它會攔截去指定的遠程 Repository 下載構件的請求,而後從本身這裏找出構件回送給客戶端。配置 Mirror 的目的通常是出於網速考慮。

RepositoryMirror 是兩個不一樣的概念:前者自己是一個倉庫,能夠堆外提供服務,然後者自己並非一個倉庫,它只是遠程倉庫的網絡加速器。

須要注意的是不少本地倉庫搭建工具每每也提供 Mirror 服務,好比Nexus就可讓同一個URL,既用做 internalrepository,又使它成爲全部 repositoryMirror

若是 倉庫X 能夠提供 倉庫Y 存儲的全部內容,那麼就能夠認爲 X是Y的一個鏡像。這也意味着,任何一個能夠從某個倉庫中得到的構件,均可以從它的鏡像中獲取。

舉個例子:http://maven.net.cn/content/groups/public/ 是中央倉庫 http://repo1.maven.org/maven2/ 在中國的鏡像,因爲地理位置的因素,該鏡像每每可以提供比中央倉庫更快的服務。

所以,能夠在Maven中配置該鏡像來替代中央倉庫。在settings.xml中配置以下代碼:

<settings>
  ...
  <mirrors>
    <mirror>
      <id>maven.net.cn</id>
      <mirrorOf>central</mirrorOf>
      <name>one of the central mirrors in china</name>
      <url>http://maven.net.cn/content/groups/public/</url>
      </mirror>
  </mirrors>
  ...
</settings>

\的值爲central,表示該鏡像是中央倉庫的鏡像,任何對於中央倉庫的請求都會轉至該鏡像,以下圖所示:

每一個程序員都該學會的Maven知識,你都能運用這些嗎?

對於鏡像的最佳實踐是結合私服。因爲私服能夠代理任何外部的公共倉庫(包括中央倉庫),所以,對於組織內部的Maven用戶來講,使用一個私服地址就等於使用了全部須要的外部倉庫,這能夠將配置集中到私服,從而簡化Maven自己的配置。在這種狀況下,任何須要的構件均可以從私服得到,私服就是全部倉庫的鏡像。

例如能夠這樣來配置一個代理全部倉庫的鏡像:

<settings>
  ...
  <mirrors>
    <mirror>
      <id>internal-repository</id>
      <name>Internal Repository Manager</name>
      <url>internal-repository-url</url>
      <mirrorOf>*</mirrorOf>
    </mirror>
  </mirrors>
  ...
</settings>

\的值爲星號,表示該鏡像是全部Maven倉庫的鏡像,任何對於遠程倉庫的請求都會被轉至: internal-repository-url 這個地址。

下面給出一張 Maven 官方的架構圖:

每一個程序員都該學會的Maven知識,你都能運用這些嗎?

生命週期

生命週期是由 一組順序階段 構成的一個總體,這麼說可能有點繞,那讓咱們來關注他裏面的幾個重要的點:

  • 一組:指的是可能有多個
  • 順序:指的是按照順序執行,執行某一個階段的指令時會依次先執行該階段以前的指令
  • 階段:指的是具體要執行的內容

例如 Maven 有三個內置的構建生命週期: defaultcleansite。每一個生命週期都由一系列的階段所構成,好比 default 生命週期的一個簡易階段以下,完整的生命週期請參考官方文檔:

每一個程序員都該學會的Maven知識,你都能運用這些嗎?

上圖中的每個節點都是一個 階段 ,階段的執行是按順序的,一個階段執行完成以後纔會執行下一個階段。好比咱們執行了一個以下的指令:

mvn install

他實際會執行 install 階段以前的全部階段,而後纔會執行 install 階段自己。

PS:當咱們的項目是多模塊的,咱們在最頂層執行該指令時,Maven 會遍歷每個子模塊,依次執行全部的階段。

座標

說到 Maven 的座標,咱們首先就須要想到 GAV ,即 groupId artifactId version。由這三個屬性就能夠惟一肯定一個jar包了。其中每一個屬性的意義以下:

  • groupId:表示一個團體,能夠是公司、組織等
  • artifactId:表示團體下的某個項目
  • version:表示某個項目的版本號

他們之間的關係是一對多的,即每一個團體下能夠有多個項目,每一個項目能夠有多個版本號,能夠用下面這張圖來表示:

每一個程序員都該學會的Maven知識,你都能運用這些嗎?

依賴

Maven 核心特色之一是依賴管理。一旦咱們開始處理多模塊工程(包含數百個子模塊或者子工程)的時候,模塊 間的依賴關係就變得很是複雜,管理也變得很困難。針對此種情形,Maven 提供了一種高度控制的方法。

依賴傳遞

依賴傳遞很好理解,假設 B 依賴於 C,當 A 須要依賴 B 時,則 A 自動得到了對 C 的依賴。依賴傳遞有時很是好,當咱們須要依賴不少jar包時,咱們能夠聲明一個包來依賴全部的jar,而後只要依賴這個包就能夠了。可是有時又很麻煩,由於極可能會形成依賴的衝突。

依賴衝突

當同一個項目中因爲不一樣的jar包依賴了相同的jar包,此時就會發生依賴衝突的狀況,以下圖所示:

每一個程序員都該學會的Maven知識,你都能運用這些嗎?

當項目中依賴了a和c,而a和c都依賴了b,這時就形成了衝突。爲了不衝突的產生,Maven 使用了兩種策略來解決衝突,分別是 短路優先聲明優先

短路優先

短路優先的意識是,從項目一直到最終依賴的jar的距離,哪一個距離短就依賴哪一個,距離長的將被忽略掉。例以下圖所示:

每一個程序員都該學會的Maven知識,你都能運用這些嗎?

聲明優先

聲明優先的意思是,經過jar包聲明的順序來決定使用哪一個,最早聲明的jar包老是被選中,後聲明的jar包則會被忽略,以下圖所示:

每一個程序員都該學會的Maven知識,你都能運用這些嗎?

依賴排除

若是咱們只想引用咱們直接依賴的jar包,而不想把間接依賴的jar包也引入的話,那可使用依賴排除的方式,將間接引用的jar包排除掉,以下面的配置所示:

<exclusions>
    <exclusion>
        <groupId>excluded.groupId</groupId>
        <artifactId>excluded-artifactId</artifactId>
    </exclusion>
</exclusions>

解決衝突

項目中出現衝突,大致都是由於上面所描述的緣由,而後 Maven 在選擇jar包時,選擇了一個錯的包,致使出現問題,這時咱們就須要人爲來干預他,告訴 Maven 使用哪一個正取的包。下面讓我舉個例子來講明衝突產生後該如何解決。

咱們本來運行得好好的一個項目,忽然有一次啓動的時候,報錯了,以下圖所示:

每一個程序員都該學會的Maven知識,你都能運用這些嗎?

能夠看到有報了一個 NoSuchMethodError ,看到這個錯,多半都是由於衝突致使的。

錯誤說的是找不到 javax.servlet.ServletContext 類中的 getVirtualServerName 方法了,那咱們在 idea 中搜索一下 javax.servlet.ServletContext 類,看看是否存在多個的狀況,以下圖所示:

每一個程序員都該學會的Maven知識,你都能運用這些嗎?

能夠發現確實在兩個jar包中都找到了 javax.servlet.ServletContext 這個類,那咱們打開他們看看哪一個類沒有咱們須要方法:

每一個程序員都該學會的Maven知識,你都能運用這些嗎?

能夠很清楚的看到,在 servlet-api-3.0.jar 包中沒有找到咱們須要的方法,而 Maven 確定是選擇了這個包。那就讓咱們來看下依賴樹吧,看看 Maven 是怎樣選擇了錯誤的包的。

在項目目錄中輸入以下指令:

mvn dependency:tree -Dverbose -Dincludes=javax.servlet:servlet-api

Maven 將打印出 servlet-api-3.0.jar 的包的依賴樹,以下圖所示:

每一個程序員都該學會的Maven知識,你都能運用這些嗎?

而後在輸入以下指令:

mvn dependency:tree -Dverbose  -Dincludes=org.apache.tomcat.embed:tomcat-embed-core`

Maven 將打印出 tomcat-embed-core-8.5.31.jar 的包的依賴樹,以下圖所示:

每一個程序員都該學會的Maven知識,你都能運用這些嗎?

咱們分析下緣由,從 Maven 中打印出的依賴樹來看,發現很奇怪的事:

servlet-api-3.0.jar 包是在 xx-service 模塊中引入的,從 xx-web 到他的深度爲6,

tomcat-embed-core-8.5.31.jar 包是在 xx-web 模塊中引入的,從 xx-web 到他的深度爲3。

那按照短路優先的規則,Maven 應該會選擇 tomcat-embed-core-8.5.31.jar 包纔對,如今沒有選擇他,那緣由確定只有一個了:聲明優先!

說明 servlet-api-3.0.jar 包比 tomcat-embed-core-8.5.31.jar 包先聲明。

咱們發現 servlet-api-3.0.jar 包是在 xx-service 中被引入的,

tomcat-embed-core-8.5.31.jar 是在 spring-boot-starter-web 中被引入的。

那隻要能證實在 xx-webxx-service 先於 spring-boot-starter-web 聲明就能夠了,讓咱們去 xx-web 中看看:

每一個程序員都該學會的Maven知識,你都能運用這些嗎?

事實證實了咱們的猜測是正確的, xx-service 確實比 spring-boot-starter-web 先聲明!

能夠用圖形表示成以下:

每一個程序員都該學會的Maven知識,你都能運用這些嗎?

那知道緣由了,要解決這個衝突,就很好辦了,有兩種方法:

  • 在 xx-web 中將 xx-service 放到 spring-boot-starter-web 後面聲明

  • 在 xx-service 中找到引入 servlet-api-3.0.jar 包將他排除掉

依賴管理

聚合

將多個項目同時運行就稱爲聚合,以下就是一個多模塊的項目:

<packaging>pom</packaging>
 <modules>
     <module>module-1</module>
     <module>module-2</module>
     <module>module-3</module>
 </modules>

聚合的優點在於能夠在一個地方編譯多個 pom 文件。

PS:聚合時 packaging 必需要是 pom

繼承

跟java類的繼承相似,Maven 的繼承特性也會繼承父pom中的依賴,假設咱們定義了一個父pom:

<groupId>com.houyi</groupId>
<artifactId>maven-parent</artifactId>
 <version>0.0.1-SNAPSHOT</version>
<dependencyManagement>
   <dependencies>
        <dependency>
            <groupId>junit</groupId> 
            <artifactId>junit</artifactId> 
            <version>${junit.version}</version> 
            <scope>test</scope>
      </dependency>
      <dependency>
            <groupId>mysql</groupId> 
              <artifactId>mysql-connector-java</artifactId> 
              <version>5.1.30</version>
      </dependency>
   </dependencies>
</dependencyManagement>

而後在子pom中引入這個父pom:

<!-- 指定parent,說明是從哪一個pom繼承 -->
<parent>
    <groupId>com.houyi</groupId> 
    <artifactId>maven-parent</artifactId> 
    <version>0.0.1-SNAPSHOT</version>
    <!-- 指定相對路徑 --> 
    <relativePath>../maven-parent</relativePath></parent>

<!-- 只須要指明groupId + artifactId,就能夠到父pom找到了,無需指明版本 -->
<dependencies>
  <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId> 
  </dependency>
  <dependency>
        <groupId>mysql</groupId> 
          <artifactId>mysql-connector-java</artifactId>
   </dependency>
</dependencies>

使用dependencyManagement,可對依賴進行管理。子類只要不引用這個裏面寫的groupId + artifactId,則不會添加依賴,這樣防止形成重複加了包:若是不使用dependencyManagement,那麼只要寫了dependency,子pom中會所有添加到依賴中,而其中不少包可能都用不上。

插件

插件是 Maven 的核心,全部執行的操做都是基於插件來完成的。

爲了讓一個插件中能夠實現衆多的相相似的功能,Maven 爲插件設定了目標,一個插件中有可能有多個目標。其實生命週期中的每一個階段都是由插件的一個具體目標來執行的

例如能夠用下面的方式配置一個插件:

<build>
   <plugins>
        <plugin>
             <groupId>org.apache.maven.plugins</groupId>
             <artifactId>maven-source-plugin</artifactId> 
             <version>2.2.1</version>
            <!-- 配置執行 -->
            <executions>
                 <execution>
                     <phase>package</phase>
                     <goals>
                        <goal>jar-no-fork</goal>
                     </goals>
                 </execution>
              </executions>
          </plugin>
       </plugins>
</build>

配置目標 goal 的目的是:這樣在執行 mvnpackage 的時候,就會自動執行 mvn source:jar-no-fork了,jar-no-fork這個目標是用來進行源碼打包的。

除了能夠在build元素中配置插件,固然也能夠在parent項目中,用pluginManagement來配置,而後在子項目繼承便可使用。

PS:經過插件咱們能夠作不少事,好比經過mybatis-generator 咱們能夠生成不少DAO層的代碼,再配合通用Mapper+lombok使用的話就可使你的代碼很是簡潔,絕對的生產力工具!

指令

下面列舉一些經常使用的 maven 指令:

每一個程序員都該學會的Maven知識,你都能運用這些嗎?

指令參數

上面列舉的只是比較通用的命令,其實不少命令均可以攜帶參數以執行更精準的任務。 Maven命令可攜帶的參數類型以下:

-D 傳入屬性參數

好比命令: mvnpackage-Dmaven.test.skip=true-D開頭,將 maven.test.skip 的值設爲 true ,就是告訴maven打包的時候跳過單元測試。

同理, mvn deploy-Dmaven.test.skip=true 表明部署項目並跳過單元測試。

-P 使用指定的Profile配置

好比項目開發須要有多個環境,通常爲開發,測試,預發,正式4個環境,在pom.xml中的配置以下:

<profiles>
  <profile>
     <id>dev</id>
     <properties>
        <env>dev</env>
     </properties>
     <activation>
        <activeByDefault>true</activeByDefault>
     </activation>
  </profile>
  <profile>
     <id>qa</id>
     <properties>
         <env>qa</env>
     </properties>
  </profile>
  <profile>
     <id>pre</id>
     <properties>
        <env>pre</env>
     </properties>
  </profile>
  <profile>
     <id>prod</id>
     <properties>
        <env>prod</env>
     </properties>
  </profile></profiles>
...
<build>
  <filters>
        <filter>config/${env}.properties</filter>
  </filters>
  <resources>
     <resource>
        <directory>src/main/resources</directory>
        <filtering>true</filtering>
     </resource>
  </resources>
</build>

profiles定義了各個環境的變量 idfilters中定義了變量配置文件的地址,其中地址中的環境變量就是上面 profile中定義的值, resources中是定義哪些目錄下的文件會被配置文件中定義的變量替換。

經過maven能夠實現按不一樣環境進行打包部署,命令爲:

mvn package -P dev

其中 dev 爲環境的變量id,表明使用Id爲 devprofile。**

-e 顯示maven運行出錯的信息

-o 離線執行命令,即不去遠程倉庫更新包

-X 顯示maven容許的debug信息

-U 強制去遠程更新snapshot的插件或依賴,默認天天只更新一次

舉個例子

將本身的jar包部署到遠程倉庫去,可使用 deploy 指令:

mvn deploy:deploy-file -DgroupId=<group-id> \
  -DartifactId=<artifact-id> \
  -Dversion=<version> \
  -Dpackaging=<type-of-packaging> \
  -Dfile=<path-to-file> \
  -DrepositoryId=<id-to-map-on-server-section-of-settings.xml> \
  -Durl=<url-of-the-repository-to-deploy>

最後說下咱們爲何要學習maven,大概能夠收穫這些好處吧:

  • 提升本身的生產力
  • 更好的管理項目中的jar包
  • 本身開發的jar包能夠共享給別人
  • 遇到jar包衝突問題能夠不求人
  • 。。。
相關文章
相關標籤/搜索