Maven 實戰

Maven

Maven 是跨平臺的項目管理工具,主要服務於基於Java平臺的項目構建、依賴管理和項目信息管理。Maven 的主要思想是約定優於配置。經過將約定項目的目錄結構,抽象項目的生命週期的方式,將程序員從繁瑣的項目構建中解放出來。java

注:本文用的是 maven-3.5.0 版本。程序員

Maven 優勢和做用

平常工做除了編寫源代碼,天天有至關一部分時間花在了項目構建中,他們包括項目的編譯、運行單元測試、生成文檔、打包和部署等煩瑣且不起眼的工做。spring

構建過程

項目自動化構建。Maven 提供一套規範以及一系列腳本,從清理、編譯、測試到生成報告,再到打包和部署實現自動化構建。還提供了插件擴展的方式,進一步簡化構建的過程。Maven 還能對項目進行編譯、測試、打包,而且將項目生成的構建部署到倉庫中。apache

Maven 是跨平臺的。對外提供了一致的操做接口,不管在 windows 平臺、Linux 平臺仍是 Mac 上都能用相同的命令進行操做。同時,Maven 項目目錄結構、測試用例命名方式等內容都有既定的規則,只要遵循了這些成熟的規則,用戶在項目間切換的時候就免去了額外的學習成本。windows

Maven 是依賴管理工具。Java 項目須要依賴許多的 jar 包,隨着依賴的增多,版本不一致、版本衝突、依賴臃腫等問題都會接踵而來。Maven 經過倉庫統一存儲這些 jar 包,並經過 pom 文件來管理這些依賴。api

Maven 是項目配置工具。Maven能幫助咱們管理本來分散在項目中各個角落的項目信息,包括項目描述、開發者列表、版本控制系統地址、許可證、缺陷管理系統地址等。緩存

Maven 的倉庫

maven 概念模型

以上是 Maven 的概念模型,前面說過 Maven 能管理衆多的 jar 包,而且梳理他們之間的依賴關係。Maven 經過 pom 文件倉庫進行實現。tomcat

倉庫的做用

若是沒有 maven 咱們要使用一個 jar 包要從項目的官網尋找下載 jar 到本地,而後再將 jar 包導入到項目中。這樣存在幾個問題:服務器

  1. 去相應的網站尋找 jar 包費精力
  2. 下載以後當須要用到某一個 jar 包的時候還要在本地找 jar 包
  3. 依賴的 jar 包有多個版本要怎麼管理

...框架

最好的解決方式就是將這些 jar 包統一管理,每次只要去一個地方找就能夠了。

Maven 就幫咱們作了這樣一件事情,他提供一個免費的中央倉庫http://repo1.maven.org/maven2,該中央倉庫包含了世界上大部分流行的開源項目。

咱們能夠從中央倉庫下載須要的 jar 包,從中央倉庫下載的 jar 包會統一保存在 maven 的本地倉庫中。本地倉庫在本機的.m2文件夾中。

本地倉庫更多相關信息能夠去搜索 maven 的安裝教程。

更多種類的倉庫

Maven 倉庫

遠程倉庫除了中央倉庫還有私服和其餘公共倉庫。

私服

私服是一種特殊的遠程倉庫,它是架設在局域網內的倉庫服務,私服代理廣域網上的遠程倉庫,供局域網內的Maven用戶使用。

Maven 私服

上圖是搭建私服的示意圖。私服會將其餘公共倉庫的 jar 緩存到搭建的服務器上,局域網內的用戶還能夠將構建的項目直接放到私服上供其餘開發人員使用。

架設私服是 Maven 推薦的作法,私服減小中央服務器的壓力。若是沒有私服,每次請求都須要向中央服務器請求,有了私服以後經過私服的服務器向中央倉庫請求,局域網內的用戶只須要向私服請求便可。

其餘公共倉庫

好比 Maven 的中央倉庫部署在國外,國內訪問外網速度不夠,咱們能夠在國內架設 Maven 的公共倉庫。

若是倉庫 X 能夠提供倉庫 Y 存儲的全部內容,那麼就能夠認爲 X 是 Y 的一個鏡像,顯然在國內咱們須要一箇中央倉庫的鏡像。http://maven.net.cn/content/groups/public/是中央倉庫,http://repo1.maven.org/maven2/在中國的鏡像。

Maven 默認是從中央倉庫下載文件的,想要讓其從其餘地方下載文件就要進行配置,這裏就須要操做 maven 的 setting.xml 文件了。

setting 文件

在安裝好 maven 的基礎上,進入 maven 的安裝目錄,能夠看到以下的目錄結構:

Maven 目錄

  • bin : mvn 的一些腳本文件
  • boot : 含有 plexus-classworlds 類加載器框架
  • conf : 配置文件
  • lib : maven 所使用的 jar 包(maven 基於 java 開發)

setting.xml 文件就在conf目錄中。這裏的setting.xml是 maven 全局的配置文件,不建議修改。修改以後會影響 maven 的升級等操做。經常使用的作法是拷貝一份setting.xml到 maven 本地倉庫的同一目錄下,而本地倉庫配置在用戶目錄的.m2文件夾中,此時的setting.xml就是用戶級別的配置文件。

強烈建議遵循以上規範,避免沒必要要的麻煩。

自定義本地倉庫位置

接下來就來看setting.xml的一些配置了。

首先localRepository定義本地倉庫位置,默認在用戶目錄下的.m2/repository中。

Default: ${user.home}/.m2/repository

<localRepository>
    /path/to/local/repo
</localRepository>

配置多個遠程倉庫

前面講過有中央倉庫和其餘遠程倉庫,配置遠程倉庫就在repositories中配置。

<repositories>
        <repository>
             <id>jboss</id>
             <name>JBoss Repository</name>
            <url>http://repository.jboss.com/maven2/</url>
            <releases>
                <enabled>true</enabled>
               <updatePolicy>daily</updatePolicy>
            </releases>
            <snapshots>
                 <enabled>false</enabled>
                 <checksumPolicy>warn</checksumPolicy>
           </snapshots>
             <layout>default</layout>
      </repository>
</repositories>

在repositories元素下,可使用repository子 元素聲明一個或者多個遠程倉庫。

配置中央倉庫鏡像

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

配置私服做爲鏡像

<mirrors>
     <mirror>
       <id>maven.oschina.net</id>
       <name>maven mirror in China</name>
      <url>http://maven.oschina.net/content/groups/public/</url>
       <mirrorOf>central</mirrorOf>
    </mirror>
</mirrors>

倉庫搜索服務

如下網站提供 Maven 倉庫搜索功能。

通常我就用最後一個搜索。

Maven 座標

如今有了倉庫統一保管這些 jar 包,剩下的問題就是怎麼取了。

不知道你有沒有取快遞的經驗。咱們能夠這些 jar 包想象成是快遞,倉庫中保管着這些快遞。咱們去認領快遞須要依靠快遞單來肯定,一張快遞單上會有單號、咱們的姓名、手機號等信息。依靠這些信息就不會領錯快遞了。

這裏的快遞單就像 Maven 中的 pom 文件,單子上的信息就像是 pom 文件中的座標系

pom.xml

Maven 項目規定的項目結構是這樣的:

  • src/main/java —— 存放項目的.java文件
  • src/main/resources —— 存放項目資源文件,如spring, hibernate配置文件
  • src/test/java —— 存放全部測試.java文件,如JUnit測試類
  • src/test/resources —— 測試資源文件
  • target —— 項目輸出位置
  • pom.xml——maven項目核心配置文件

每一個 maven 項目都有 pom.xml 文件。Maven座標爲各類構件引入了秩序,任何一個構件都必須明肯定義本身的座標。

座標系

一組 Maven座標是經過一些元素定義的,它們是 groupId、artifactId、version、packaging、 classifier:

  • groupId:定義當前Maven項目隸屬的實際項目,一般爲域名反寫
  • artifactId:該元素定義實際項目中的一個 Maven項目(模塊),推薦的作法是使用實際項目名稱做爲artifactId的前綴。
  • version:該元素定義Maven項目當前所處的 版本,
  • packaging:該元素定義Maven項目的打包方 式。當不定義packaging的時候,Maven會使用默認值jar。
  • classifier:該元素用來幫助定義構建輸出的一些附屬構件。

經過座標系咱們來保證項目在 Maven 倉庫中的惟一性,每次取也不會取錯了。

Maven 依賴

咱們本身項目須要用別人的 jar 包,好比 spring。這就是咱們的項目依賴於 spring,所以咱們經過 pom 來配置這樣的依賴關係,這樣就能讓項目有清晰的結構。

依賴的關係用用<dependecy>標籤來表示依賴:

dependencies

上圖說明該項目依賴了 hibernate 等

依賴範圍

如今來考慮一種狀況,咱們在項目開發的過程當中用到了 junit 進行測試,也就是說咱們的項目依賴於 junit。在項目構建的過程當中咱們會把 junit 也打包在項目中。可是在生產環境中徹底沒有必要用到 junit,咱們並不想將它發佈到生產環境中。

咱們能夠每次在發佈項目以前把他刪除了對麼?那若是依賴 servlet-api,咱們只有在編譯和測試項目的時候須要該依賴,但在運行項目的時候,因爲容器已經提供,也不須要 Maven 重複地引入一遍。

因此最好是在編譯、測試、運行的過程當中須要用到什麼 jar 包,就讓 Maven 去打包什麼。

maven 爲此提供了scope標籤表示依賴範圍,表示該 jar 包在何時須要被使用。

依賴範圍

  • compile:編譯依賴範圍,使用此依賴範圍對於編譯、測試、運行三種classpath都有效,即在編譯、測試和運行時都要使用該依賴jar包;
  • test:測試依賴範圍,只對測試有效,代表只在測試的時候須要,在編譯和運行時將沒法使用該類依賴,如 junit;
  • provided:已提供依賴範圍。編譯和測試有效,運行無效。如servlet-api,在項目運行時,tomcat等容器已經提供,無需Maven重複引入;
  • runtime:運行時依賴範圍。測試和運行有效,編譯無效。如 jdbc 驅動實現,編譯時只需接口,測試或運行時才須要具體的 jdbc 驅動實現;
  • system:系統依賴範圍,使用system範圍的依賴時必須經過systemPath元素顯示地指定依賴文件的路徑,不依賴Maven倉庫解析,因此可能會形成建構的不可移植,謹慎使用。

依賴傳遞

依賴範圍除了控制classpath,還會對依賴傳遞產生影響。若是A依賴B,B依賴C,則A對於B是第一直接依賴。B對於C是第二直接依賴。A對於C是傳遞性依賴。結論是:第一直接依賴的範圍和第二直接依賴的範圍決定了傳遞性依賴的範圍。

依賴傳遞

第一列是第一直接依賴,第一行是第二直接依賴,中間表示傳遞性依賴範圍。

此外 maven 還提供了optionexclusions來進一步管理依賴,分別稱爲可選依賴排除依賴

可選依賴

在依賴中添加 <optional> true/false <optional> 表示是否向下傳遞。

如上圖所示,B 依賴於 X,Y 而 A 依賴於 B,若是 B 不但願將依賴傳遞給 A 則能夠配置 B 中的 X,Y 依賴的optional爲 true 來阻止依賴的傳遞。

排除依賴

再來看一種狀況,A 依賴於 B,且 B 將他的依賴 C 傳遞給了 A。可是 A 依賴了 C 的另外一個版本。這個時候 A 能夠主動排除 B 給的 C 依賴,轉而使用本身須要的版本,這就用到了exclusions標籤。

用exclusions元素聲明排除依賴,exclusions能夠包 含一個或者多個exclusion子元素,所以能夠排除一個或者多個傳遞性依賴。

因此我用主動和被動的方式來區分他們。

依賴衝突

接上面的問題,若是 A 和 B 依賴 C 的不一樣版本,並且既沒有配置可選依賴也沒有配置排除依賴。兩個版本都被解析顯然是不對的,由於那會形成依賴重複,所以必須選擇一個。

路徑最近者優先。若是直接與間接依賴中包含有同一個座標不一樣版本的資源依賴,以直接依賴的版本爲準。

第一聲明者優先。在依賴路徑長度相等的前 提下,在POM中依賴聲明的順序決定了誰會被解析使用,順序最靠前的那個依賴優勝。

上面例子中,A -> C(1.10) 和 A -> B -> C(?),C(1.10)的路徑短因此用它。

Maven 生命週期

Maven的生命週期就是爲了對全部的構建過程進行抽象和統一。這個生命週期包含了項目的 清理、初始化、編譯、測試、打包、集成測試、 驗證、部署和站點生成等幾乎全部構建步驟。

初學者每每會覺得Maven的生命週期是一個總體,其實否則。Maven擁有三套相互獨立的生命週期,它們分別爲clean、default和site。clean生命週期的目的是清理項目,default生命週期的目的是構建項目,而site生命週期的目的是創建項目站點。

clean 生命週期。

clean生命週期的目的是清理項目,它包含三個階段:

  • pre-clean 執行一些須要在clean以前完成的工做
  • clean 移除全部上一次構建生成的文件
  • post-clean 執行一些須要在clean以後馬上完成的工做

mvn clean 中的clean就是上面的clean,在一個生命週期中,運行某個階段的時候,它以前的全部階段都會被運行,也就是說,mvn clean 等同於 mvn pre-clean clean ,若是咱們運行 mvn post-clean ,那麼 pre-clean,clean 都會被運行。這是Maven很重要的一個規則,能夠大大簡化命令行的輸入。

default生命週期

default生命週期定義了真正構建時所須要執 行的全部步驟,它是全部生命週期中最核心的部分,其包含的階段以下:

  • validate
  • generate-sources
  • process-sources
  • generate-resources
  • process-resources 複製並處理資源文件,至目標目錄,準備打包。
  • compile 編譯項目的源代碼。
  • process-classes
  • generate-test-sources
  • process-test-sources
  • generate-test-resources
  • process-test-resources 複製並處理資源文件,至目標測試目錄。
  • test-compile 編譯測試源代碼。
  • process-test-classes
  • test 使用合適的單元測試框架運行測試。這些測試代碼不會被打包或部署。
  • prepare-package
  • package 接受編譯好的代碼,打包成可發佈的格式,如 JAR 。
  • pre-integration-test
  • integration-test
  • post-integration-test
  • verify
  • install 將包安裝至本地倉庫,以讓其它項目依賴。
  • deploy 將最終的包複製到遠程的倉庫,以讓其它開發人員與項目共享。

運行任何一個階段的時候,它前面的全部階段都會被運行,這也就是爲何咱們運行mvn install 的時候,代碼會被編譯,測試,打包。此外,Maven的插件機制是徹底依賴Maven的生命週期的,所以理解生命週期相當重要。

site生命週期

site生命週期的目的是創建和發佈項目站點,Maven可以基於POM所包含的信息,自動生成一個友好的站點,方便團隊交流和發佈項目信息。

  • pre-site 執行一些須要在生成站點文檔以前完成的工做

site 生成項目的站點文檔

  • post-site 執行一些須要在生成站點文檔以後完成的工做,而且爲部署作準備
  • site-deploy 將生成的站點文檔部署到特定的服務器上

這裏常常用到的是site階段和site-deploy階段,用以生成和發佈Maven站點,這但是Maven至關強大的功能,Manager比較喜歡,文檔及統計數據自動生成,很好看。

命令與生命週期

mvn clean:該命令調用clean生命週期的clean階段。實際執行的階段爲clean生命週期的 pre-clean和clean階段。

mvn test:該命令調用default生命週期的test階段。實際執行的階段爲default生命週期的 validate、initialize等,直到test的全部階段。這也解釋了爲何在執行測試的時候,項目的代碼可以自動得以編譯。

mvn clean install:該命令調用clean生命週期 的clean階段和default生命週期的install階段。

mvn clean deploy site-deploy:該命令調用 clean生命週期的clean階段、default生命週期的 deploy階段,以及site生命週期的site-deploy階段。

聚合與繼承

軟件設計人員每每會採用各類方式對軟件劃分模塊,以獲得更清晰的設計及更高的重用性。當把Maven應用到實際項目中的時候,也須要將項目分紅不一樣的模塊。

簡單的說就是有 A,B 兩個模塊,如今想要將他們統一管理。Maven 的聚合特性可以把項目的各個模塊聚合在一塊兒構建,而Maven的繼承特性則能幫助抽取各模塊相同的依賴和插件等配置,在簡化POM的同時,還能促進各個模塊配置的一致性。

聚合

兩個子模塊但願同時構建。這時,一個簡單的需求就會天然而然地顯現出來:咱們會想要一次構建兩個項目,而不是到兩個模塊的目錄下分別執行mvn命令。Maven聚合(或者稱爲多模塊)這一特性就是爲該需求服務的。

項目結構

上圖所示api是一個模塊,cmd是一個模塊他們都有各自的 pom 文件,其實每個包都是一個子模塊,而最底下的 pom 文件則是統一管理這些子模塊。

他們的配置很簡單,咱們最好遵循規範。

api 的 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>com.shuiyujie.fu</groupId>
        <artifactId>sop</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <modelVersion>4.0.0</modelVersion>
    <artifactId>api</artifactId>
    ...

cmd 的 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>com.shuiyujie.fu</groupId>
        <artifactId>sop</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <modelVersion>4.0.0</modelVersion>
    <artifactId>cmd</artifactId>

聚合 pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>com.shuiyujie.fu</groupId>
    <artifactId>pom</artifactId>
    <packaging>sop</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>sop</name>
    <url>http://maven.apache.org</url>
    ...    
        <modules>
        <module>base</module>
        <module>core</module>
        <module>server</module>
        <module>persist</module>
        <module>api</module>
        <module>impl</module>
        <module>cmd</module>
    </modules>
    ...

觀察上面的三個代碼清單能夠聚合 pom 文件中定義了 <modules> 標籤,標籤中包含的就是各個子模塊,而且用子模塊的artifactId來標記他們。

注意:聚合 pom 文件的打包方式,即 packaging 必須爲 pom。

這樣只須要構建聚合 pom 文件便可同時構建在其管理下的多個子模塊。

繼承

消除重複。在面向對象世界中,程序員可使用類繼承在必定程度上消除重複,在Maven的世界 中,也有相似的機制能讓咱們抽取出重複的配 置,這就是POM的繼承。

任然看上面的三個 pom.xml 代碼清單,子模塊都有一個parent標籤,這就代表他們繼承了一個 pom 文件,而parent標籤下的其餘標籤就是一個座標系,經過一個座標系就能定位一個惟一的項目。

好比上面的子模塊繼承自聚合 pom 文件,因此此時聚合 pom 文件也是父類 pom 文件

排除父類的依賴

在繼承的過程當中咱們考慮一種情形,咱們但願在父類中統一控制 spring 的版本,而後子類繼承自父類就可使用統一版本的 spring 依賴了。可是有些子模塊不須要依賴 spring,並不須要從父類繼承 spring 的依賴。

咱們可使用dependencyManagement 標籤。

父類 pom.xml

<dependencyManagement>
        <dependencies>
            <!-- 模塊間依賴 start -->
            <dependency>
                <groupId>${project.groupId}</groupId>
                <artifactId>core</artifactId>
                <version>${project.version}</version>
            </dependency>
            <dependency>
                <groupId>${project.groupId}</groupId>
                <artifactId>persist</artifactId>
                <version>${project.version}</version>
            </dependency>
            <dependency>
                <groupId>${project.groupId}</groupId>
                <artifactId>api</artifactId>
                <version>${project.version}</version>
            </dependency>
            <dependency>
                <groupId>${project.groupId}</groupId>
                <artifactId>impl</artifactId>
                <version>${project.version}</version>
            </dependency>
            <dependency>
                <groupId>${project.groupId}</groupId>
                <artifactId>server</artifactId>
                <version>${project.version}</version>
            </dependency>
            <dependency>
                <groupId>${project.groupId}</groupId>
                <artifactId>cmd</artifactId>
                <version>${project.version}</version>
            </dependency>
            <!-- 模塊間依賴 end -->
        </dependencies>
    </dependencyManagement>

父類dependencyManagement中聲明瞭各個子模塊,子模塊之間有的會須要相互引用,有的卻並不須要。因此在父類中統一配置各個子模塊的groupId,artifactId,version等基本信息。

dependencyManagement中聲明的依賴不會在當前pom中引入依賴,也不會再繼承他的pom中引入依賴,他的做用只是聲明瞭可能要引入依賴的一些通用信息。

若是要使用一個子模塊要使用其餘子模塊就能夠另外聲明,可是不須要指定版本等通用信息,這樣就能夠減小依賴衝突的發生,代碼以下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>com.shuiyujie.fu</groupId>
        <artifactId>sop</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <modelVersion>4.0.0</modelVersion>
    <artifactId>api</artifactId>

    <dependencies>
        <dependency>
            <groupId>${project.parent.groupId}</groupId>
            <artifactId>fu-persist</artifactId>
        </dependency>
    </dependencies>
</project>

總結:Maven 的思想

Maven 的核心思想是約定優於配置。

首先,Maven 約定了項目的結構,咱們不須要配置 Maven 編譯、打包等操做時文件的位置。統一的項目結構下降了學習的成本,讓我能將精力集中到了項目自己。

其次,Maven 抽象了項目構建的過程,將其分紅一個個生命週期進行管理。經過命令和插件的形式進一步簡化操做,又讓咱們從繁瑣的操做解放出來。

參考

《Maven 實戰》

Maven遠程倉庫的各類配置

本文大部份內容來自於《Maven 實戰》一書,想要了解一手信息強烈建議閱讀。網上的其餘文章基本上都是摘抄《Maven 實戰》的部份內容。

因此還想說一遍:發現一本好書就像發現了一座寶藏。

相關文章
相關標籤/搜索