歡迎光臨
每天分享高質量文章

把Maven的架構,用法,坑點介紹的清清楚楚

來自:Java識堂(微訊號:erlieStar)

沒有Maven之前的日子

個人的一個小感受,學習一個新技術,應該以歷史的眼光開看待這個新技術出現的原因,以及幫我們解決了什麼問題。我們來回憶一下沒有Maven的日子是怎麼樣的?

  1. 開發一個專案,需要用別人寫好的jar包,我們先把開源的jar包下載下來放到專案的lib目錄下,並把這個目錄新增到CLASSPATH(告訴Java執行環境,在哪些目錄下可以找到你要執行的Java程式需要的類或者包)
  2. 我們下載了a.jar發現a.jar還需要依賴b.jar,結果又去把b.jar包下載下來開始執行
  3. 如果運氣夠好,我們的專案在新增完所有的依賴後,能正產運行了。如果運氣差點,還會遇到版本的問題,例如a.jar在呼叫b.jar的時候發現b.jar根本沒有這個方法,在別的版本中才有,現在好了,光找依賴和適配版本就能花上不少時間
  4. 而且我們往git上上傳程式碼的時候,還必須把這些lib都上傳上去。別人下載我們的程式碼時也必須把lib下載下來,這個真心耗費時間

這時候Maven作為Java世界的包管理工具出現了,當然Java世界還有其他包管理工具,例如gradle等。就像yum是Linux世界的包管理工具,webpack是前端世界的包管理工具一樣

Maven倉庫的種類


Maven找jar包的過程是這樣的,先在本地倉庫找,找不到再去私服(如果配置了的話),再找不到去中央倉庫(http://repo1.maven.org/maven2/,maven團隊負責維護)

從中央倉庫找到後,會在私服和本地倉庫放一份,從私服找到後也會在本地倉庫放一份

當你安裝在好了Maven以後,在conf目錄下有個settings.xml檔案,這個裡面配置的項很多,後文會詳細介紹這個配置檔案。


在這個配置檔案下有這樣一段話,說了Maven預設的本地倉庫地址為${user.home}/.m2/repository(當然你可以重新設定本地倉庫的地址,上面就是模板),我是window電腦,來看看這個目錄


看到有很多jar包被存到本地,當然如果你想配置私服也是在settings.xml上進行配置,隨便一搜很多教程,不再贅述

搭建私服好處多多,在一個公司內部可以開發一些公共的基礎元件放到私服上,方便其他同事使用

Maven的預設配置

一個Maven的專案的整體結構是這樣的

在這裡插入圖片描述


為什麼一個Maven專案的檔案結構是這種的呢?
這就不得不說到Maven的一個特性,約定優於配置。

Maven預設配置了${project.basedir}/src/main/java為專案的原始碼目錄
${project.basedir}/src/main/test為專案的測試程式碼目錄
${project.basedir}/target為專案的編譯輸出目錄等

spring boot就是約定優於配置的體現,想想我們用spring mvc的時候還得配置檢視解析器,包的自動掃描,而用了spring boot框架,我們就完全不用再配置了

Maven專案詳解

安裝還是挺簡單的,我就不再介紹,我也沒有單獨下載,一般就用了Idea自帶的Maven了,下載完後目錄結構如下:

bin目錄:
該目錄包含了mvn執行的指令碼,這些指令碼用來配置java命令,準備好classpath和相關的Java系統屬性,然後執行Java命令。

boot目錄:
該目錄只包含一個檔案,該檔案為plexus-classworlds-2.5.2.jar。plexus-classworlds是一個類載入器框架,相對於預設的java類載入器,它提供了更加豐富的語法以方便配置,Maven使用該框架載入自己的類庫。

conf目錄:
該目錄包含了一個非常重要的檔案settings.xml。直接修改該檔案,就能在機器上全域性地定製maven的行為,即對所有使用者都生效。一般情況下,我們更偏向於複製該檔案至~/.m2/目錄下(~表示使用者家目錄,windows下~就是C:UsersPeng,Peng是小編的使用者名稱),然後修改該檔案,在使用者級別定製Maven的行為。

lib目錄:
該目錄包含了所有Maven執行時需要的Java類庫,Maven本身是分模組開發的,因此使用者能看到諸如maven-core-3.0.jar、maven-model-3.0.jar之類的檔案,此外這裡還包含一些Maven用到的第三方依賴如commons-cli-1.2.jar、commons-lang-2.6.jar等等。、

settings.xml配置檔案詳解

我們來詳細說一下settings.xml這個檔案,這個檔案可以定製Maven的行為,上面已經說到settings.xml可以放在2個位置,~/.m2/setting.xml(預設沒有,需要我們自己複製)和${maven.home}/conf/setting.xml

這2個配置檔案的載入順序為~/.m2/setting.xml>${maven.home}/conf/setting.xml,為了不影響他人,所以我們將conf下的settings.xml複製到家目錄,在使用者級別定製Maven的行為。

這個和配置環境變數有點類似,Windos和Linux都可以配置系統級別的環境變數和使用者級別的環境變數,這裡單說一下Linux的吧,在/etc/profile裡面配置的就是系統級別的環境變數,在~/.bash_profile裡面配置的就是使用者級別的環境變數

各種配置項還是挺多的,設定映象倉庫(國內用阿裡雲的比較多),設定代理,不再贅述

maven常用命令

命令 描述
mvn -version 顯示版本資訊
mvn clean 刪除target目錄
mvn compile 編譯src/main/java下的原始碼
mvn package 打包,在target下生成jar包或者war包
mvn test 執行src/test/java下以Test開頭或者以Test結尾的類的測試用例
mvn install 打包,並把jar包或者war包複製到本地倉庫,供其他模組使用
mvn deploy 將打包的檔案釋出到私服
mvn dependency:tree 打印出專案的整個依賴樹

當然也可以連著使用
mvn clean package 清理打包
mvn clean package -DskipTests=true 清理打包,並跳過測試用例
mvn clean install 清理打包,並將jar包或者war包複製到本地倉庫

執行單測的時候也沒必要一個一個點測試方法,mvn test 一個命令跑完所有測試用例,
要註意的是隻會執行以Test開頭或者結尾的測試類,也沒必要自己寫測試類,我在推薦閱讀第一篇文章中演示了快速生成測試類的方法,可以去看看,生成的測試類都是以Test結尾的

mvn dependency:tree > show.txt 將依賴輸出重定向到檔案中,方便檢視

pom.xml詳解

groupId 公司域名倒過來
artifactId 功能命名
version 版本號

這三個維度確定一個jar包,就像用(x,y,z)坐標在三維空間中唯一確定一個點。

packaging 打包方式,jar,war,maven-plugin(開發maven外掛)

scope詳解

引數 解釋 是否會被打入最終的jar包
compile 預設的scope
test 測試使用
provided 編譯需要
runtime 編譯不需要,執行時需要(介面與實現分離)
system 載入本地jar

類似如下這種,沒有指定scope,說明scope是compile

<dependency>
    <groupId>org.mybatis.spring.bootgroupId>

<artifactId>mybatis-spring-boot-starterartifactId>
<version>1.3.2version>
dependency>

test是指在執行測試用例的時候才會用到,沒必要打入到最後的jar裡面,所以你看到的測試框架的scope基本上都是test

<dependency>
    <groupId>org.springframework.bootgroupId>

<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>

provided,編譯的時候會用到,但不會被打入最後的jar包

例如想把spring boot專案以war包的形式放在tomcat中執行,首先得加入如下依賴

<dependency>
    <groupId>org.springframework.bootgroupId>

<artifactId>spring-boot-starter-tomcatartifactId>
<scope>providedscope>
dependency>

或者你寫了一個放在Storm叢集或者Flink叢集上執行的任務,最後都要把Storm的依賴或者Flink的依賴設定成provided,因為叢集上已經都有這些環境的jar包、

如果你用到lombok外掛的話,你會發現lombok的Maven是如下形式,說明它只會編譯的時候會用到。

<dependency>
    <groupId>org.projectlombokgroupId>

<artifactId>lombokartifactId>
<version>1.16.6version>
<scope>providedscope>
dependency>

我寫瞭如下一個測試類

@Data
public class Test {

    private String name;
    private int age;
}

生成的class檔案反編譯後的如下,驗證了我們的想法,編譯之後確實沒有必要再用lombok這個jar包

public class Test {
    private String name;
    private int age;

    public Test() {
    }

    public String getName() {
        return this.name;
    }

    public int getAge() {
        return this.age;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

runtime,執行時才會用到。例如,如果你的專案有對資料庫的操作,但沒有加入相應的JDBC的實現jar包,如mysql-connector-java,是可以編譯成功的,只有執行時才會報錯。所以你看到的JDBC實現的jar包scope為runtime,表明這個jar包在執行時才會用到

<dependency>
    <groupId>mysqlgroupId>

<artifactId>mysql-connector-javaartifactId>
<version>5.1.35version>
<scope>runtimescope>
dependency>

system,本地載入jar,當你和第三方公司合作,他們只是給了你一個jar包時,你可以有三種選擇

  1. mvn install到本地倉庫
  2. mvn deploy到私服
  3. 指定jar包路徑,從本地載入,例如如下pom形式

     

 <dependency>
    <groupId>com.tievd.thirdgroupId>

<artifactId>arcvideoartifactId>
<version>1.0version>
<scope>systemscope>
<systemPath>${basedir}/lib/face-api-1.0.jarsystemPath>
dependency>

前文已經說到scope為system的依賴不會被打入最終的jar包,得透過配置外掛等方式將依賴打入最終的jar包,所以這種方式一般很少使用。

Maven jar包衝突如何解決?

依賴傳遞

假設我們現在有一個多模組專案,依賴關係如圖,我們在st-web模組中引入st-dal依賴時,st-common-lib這個依賴也會被我們引入,這個就是依賴傳遞,下表中列出了scope在依賴過程中發生的變化,列標題為被依賴的模組的scope

compile test provided runtime
compile compile runtime
test test test
provided provided provided provided
runtime runtime runtime

依賴仲裁

依賴仲裁就是當專案中引入的jar包,groupId (公司域名倒過來)和artifactId (功能命令)一樣,但是version不一樣,應該選用哪一個version?也經常被人叫做依賴衝突

最短路徑原則

假如說我們現在的專案依賴關係如圖?那麼maven會選用st-common-lib的那個版本呢?

答案是1.1這個版本,st-web到st-common-lib(1.1)的距離為1,st-web到st-common-lib(1.0)的距離為2,選擇距離短的,即最短路徑原則


如何看依賴的距離關係呢?前文說過,執行如下命令打印出全域性的依賴樹,層級關係特別清楚


mvn dependency:tree > show.txt 

宣告優先原則

專案依賴如圖,路徑一樣,會選用st-common-lib的哪個版本呢?這就得看你在pom檔案中先宣告是哪個依賴,如果在pom.xml中,st-remote-invoker寫在前面,就會用1.0這個版本,如果st-dal寫在前面,則會用1.1這個版本

依賴排除

去掉間接引入的jar包

如不想用spring boot預設提供的log,想整合第三方的log時,或者說上面依賴仲裁的第二個例子中,只想用st-common-lib的1.1版本,就可以把1.0版本排除掉

<dependency>
    <groupId>org.springframework.bootgroupId>


    <artifactId>spring-boot-starterartifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-loggingartifactId>
        exclusion>
    exclusions>
dependency>

已同步到看一看
贊(0)

分享創造快樂