Maven的愛恨情仇

作者:xcbeyond
瘋狂源自夢(mèng)想,技術(shù)成就輝煌!微信公眾號(hào):《程序猿技術(shù)大咖》號(hào)主,專注后端開發(fā)多年,擁有豐富的研發(fā)經(jīng)驗(yàn),樂于技術(shù)輸出、分享,現(xiàn)階段從事微服務(wù)架構(gòu)項(xiàng)目的研發(fā)工作,涉及架構(gòu)設(shè)計(jì)、技術(shù)選型、業(yè)務(wù)研發(fā)等工作。對(duì)于Java、微服務(wù)、數(shù)據(jù)庫、Docker有深入了解,并有大量的調(diào)優(yōu)經(jīng)驗(yàn)。

前言

    在如今的互聯(lián)網(wǎng)項(xiàng)目開發(fā)當(dāng)中,特別是Java開發(fā)中,可以說Maven是隨處可見。Maven的倉(cāng)庫管理、依賴管理、繼承和聚合等特性為項(xiàng)目的構(gòu)建提供了一整套完善的解決方案,可以說如果你搞不懂Maven,那么一個(gè)多模塊的項(xiàng)目足以讓你頭疼,依賴沖突就會(huì)讓你不知所措,甚至搞不清楚項(xiàng)目是如何運(yùn)行起來的。相信使用過Maven的人,一定曾經(jīng)被Maven傷害過,但又不得不去讓它來傷害,誰讓它能給項(xiàng)目的構(gòu)建提供便利呢。

    最近在微信群中,不斷有人在使用Maven構(gòu)建項(xiàng)目時(shí),遇到了各種問題。一些問題也是大家經(jīng)常遇見的,在此,博主就Maven的愛恨情仇,來說道說道,使用時(shí)能夠得心應(yīng)手。

 
為什么要使用Maven

    通常在一個(gè)項(xiàng)目中,我們會(huì)使用一些第三方類庫,來提高開發(fā)速度,而不是閉門造車,畢竟在當(dāng)今軟件飛速發(fā)展的潮流下,不斷涌現(xiàn)、開源出一些優(yōu)秀的類庫,供咱們靈活使用。在未使用Maven時(shí),通常需要在項(xiàng)目中建立一個(gè)lib目錄,在其中放著項(xiàng)目所依賴的各種類庫,這樣提交到SVN或GIT之后, 每個(gè)開發(fā)人員檢出項(xiàng)目到本地,這樣所有開發(fā)人員就會(huì)擁有統(tǒng)一的依賴類庫。 但這樣務(wù)必面臨著下面這些問題:
1、依賴冗余,浪費(fèi)空間

    隨著公司項(xiàng)目的變大、變多,模塊的增多,這種方式就會(huì)面臨一些問題。不同項(xiàng)目,不同模塊都可能會(huì)引用相同的依賴。當(dāng)每個(gè)模塊都把自己的依賴提交到SVN或GIT,那么相同的依賴就會(huì)占用服務(wù)器SVN或GIT的Repository很大的空間,造成空間浪費(fèi)。
2、版本問題

    如果一個(gè)項(xiàng)目中依賴的版本和另一個(gè)項(xiàng)目依賴的版本不一致。比如這個(gè)項(xiàng)目依賴spring-boot-starter-parent-1.5.9.RELEASE,而另一個(gè)可能依賴spring-boot-starter-parent-2.0.0.RELEASE, 當(dāng)合并兩個(gè)項(xiàng)目發(fā)布的時(shí)候,可能因?yàn)檫@種依賴類庫詳細(xì)版本信息的缺失,造成版本混亂沖突等問題。

3、管理問題

    隨著項(xiàng)目的延續(xù),項(xiàng)目依賴的類庫可能需要更新,這時(shí)就需要不斷從網(wǎng)上或通過其他途徑,來替換lib目錄下依賴的類庫jar包文件,給依賴類庫的管理帶來了不便。

 

    為了解決上面依賴類庫管理過程中出現(xiàn)的問題,我們需要尋求另一種依賴管理方式,即:一種集中式的依賴管理方式。各個(gè)項(xiàng)目只要通過統(tǒng)一的依賴描述文件(pom.xml)來指定自己需要的依賴就可以, 而不用自己來管理真正的依賴庫,因?yàn)樗械捻?xiàng)目都使用了同一個(gè)中央依賴庫(中央倉(cāng)庫), 所以即使各個(gè)項(xiàng)目中有相同的依賴, 也不會(huì)出現(xiàn)依賴冗余的問題。 在依賴類庫需要升級(jí)時(shí),只需修改pom.xml即可方便升級(jí)更新。這種新的依賴管理方式,則是Maven,是基于POM的一款進(jìn)行項(xiàng)目依賴管理,構(gòu)建管理和項(xiàng)目信息管理的工具。

 

        回想一下,當(dāng)你新到一家公司或新進(jìn)入一個(gè)項(xiàng)目,安裝完JDK后就會(huì)安裝配置Maven,或許需要修改settings.xml文件,比如你可能會(huì)從其他人那里copy一段配置到你的settings.xml中(私服的一些配置)。接下來,你會(huì)到IDEA或者Eclipse中進(jìn)行Maven插件配置,然后你就可以在工程中的pom.xml里面開始添加、修改<dependency>標(biāo)簽來管理jar包,在Maven規(guī)范的目錄結(jié)構(gòu)下進(jìn)行編寫代碼,最后你會(huì)通過插件的方式來進(jìn)行測(cè)試、打包、部署、運(yùn)行。

 

    上面講述了我們?yōu)槭裁词褂肕aven,什么時(shí)候使用它,下面就開始看看它的一些使用方法、常見問題:
1、本地倉(cāng)庫?Maven到底有哪些倉(cāng)庫?它們什么關(guān)系?

 

本地倉(cāng)庫路徑配置:

    <localRepository>E:/.m2/repository</localRepository>

    你要的依賴jar包,不可能每次都要從互聯(lián)網(wǎng)去下載,特別是有的公司或項(xiàng)目根本就沒有外網(wǎng)的情況,多費(fèi)勁,所以本地倉(cāng)庫就是相當(dāng)于加了一層jar包緩存,先到這里來查。如果這里查不到,那么就去私服上找,如果私服也找不到,那么去中央倉(cāng)庫去找,找到j(luò)ar后,會(huì)把jar的信息同步到私服和本地倉(cāng)庫中。

    私服:就是公司內(nèi)局域網(wǎng)的一臺(tái)服務(wù)器而已,當(dāng)你的工程Project-A依賴別人的Project-B的接口,怎么做呢?沒有Maven的時(shí)候,當(dāng)然是copy Project-B jar到你的本地lib中引入,那么Maven的方式,很顯然需要其他人把Project-B deploy到私服倉(cāng)庫中供你使用。因此私服中存儲(chǔ)了本公司的內(nèi)部專用的jar,不僅如此,私服還充當(dāng)了中央倉(cāng)庫的鏡像,說白了就是一個(gè)倉(cāng)庫代理!

    中央倉(cāng)庫:該倉(cāng)庫位于互聯(lián)網(wǎng)上,由Maven團(tuán)隊(duì)來維護(hù),地址是http://repo1.maven.org/maven2/。此外,像阿里也對(duì)外提供了中央倉(cāng)庫,地址是http://maven.aliyun.com/nexus/content/groups/public,下載速度比Maven的還快,推薦使用。

 
2、關(guān)于<dependency>的使用

pom.xml依賴

    <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
          <version>2.0.0.RELEASE</version>
    </dependency>

    從上圖可以看出,通過groupId、artifactId、version,就可以在倉(cāng)庫中查找到依賴jar的位置。

    一般而言,我們可以到私服上輸入artifactId進(jìn)行搜索,或者到http://search.maven.org/、http://mvnrepository.com/上進(jìn)行查找確定依賴jar。

 

version分為開發(fā)版本(Snapshot)和發(fā)布版本(Release),那么為什么要分呢?

    在實(shí)際開發(fā)中,我們經(jīng)常遇到這樣的場(chǎng)景,比如A服務(wù)依賴于B服務(wù),A和B同時(shí)開發(fā),B在開發(fā)中發(fā)現(xiàn)了BUG,修改后,將版本由1.0升級(jí)為2.0,那么A必須也跟著在POM.XML中進(jìn)行版本升級(jí)。過了幾天后,B又發(fā)現(xiàn)了問題,進(jìn)行修改后升級(jí)版本發(fā)布,然后通知A進(jìn)行升級(jí)...可以說這是開發(fā)過程中的版本不穩(wěn)定導(dǎo)致了這樣的問題。

    Maven,已經(jīng)替我們想好了解決方案,就是使用Snapshot版本,在開發(fā)過程中B發(fā)布的版本標(biāo)志為Snapshot版本,A進(jìn)行依賴的時(shí)候選擇Snapshot版本,那么每次B發(fā)布的話,會(huì)在私服倉(cāng)庫中,形成帶有時(shí)間戳的Snapshot版本,而A構(gòu)建的時(shí)候會(huì)自動(dòng)下載B最新時(shí)間戳的Snapshot版本!

 
3、pom.xml進(jìn)行了依賴配置,本地倉(cāng)庫已經(jīng)下載下來了,為什么還會(huì)出現(xiàn)依賴沖突?

    明明已經(jīng)在pom.xml中進(jìn)行了依賴配置,檢查本地倉(cāng)庫發(fā)現(xiàn)依賴包已經(jīng)存在,卻突然提示依賴沖突或某個(gè)類找不到。此時(shí)可以采取把本地倉(cāng)庫中對(duì)應(yīng)的依賴清空,重新下載即可。

 
4、引入依賴的最佳實(shí)踐,提前發(fā)現(xiàn)問題!

    在項(xiàng)目工程中,我們避免不了不斷增加一些依賴,也許加了依賴之后運(yùn)行時(shí)才發(fā)現(xiàn)存在依賴沖突再去解決,似乎有點(diǎn)晚!那么能不能提前發(fā)現(xiàn)問題呢?






    如果我們新加入一個(gè)依賴的話,那么先通過mvn dependency:tree命令形成依賴樹,看看我們新加入的依賴,是否存在傳遞依賴,傳遞依賴中是否和依賴樹中的版本存在沖突,如果存在多個(gè)版本沖突,再逐步解決!

 
5、Maven規(guī)范化目錄結(jié)構(gòu)

上圖為Maven項(xiàng)目的規(guī)范目錄結(jié)構(gòu)。

需要有下面兩點(diǎn)注意:

① src/main下內(nèi)容最終會(huì)打包到Jar/War中,而src/test下是測(cè)試內(nèi)容,并不會(huì)打包進(jìn)去。

② src/main/resources中的資源文件會(huì)COPY至目標(biāo)目錄,這是Maven的默認(rèn)生命周期中的一個(gè)規(guī)定動(dòng)作。(想一想,hibernate/mybatis的映射XML需要放入resources下,而不能在放在其他地方了)

 
6、Maven的生命周期

如上圖所示Maven的生命周期包括:clean、validate、compile、test、package、verify、install、site、deploy,其中需要注意的是:執(zhí)行后面的命令時(shí),前面的命令自動(dòng)得到執(zhí)行,(其中,也可以跳過其中的步驟,如:test)。

    clean:清理。有問題,多清理!
    validate:驗(yàn)證。驗(yàn)證項(xiàng)目是否正確。
    compile:編譯。執(zhí)行編譯,源代碼編譯在此階段完成。
    test:測(cè)試。使用適當(dāng)?shù)膯卧獪y(cè)試框架(例如JUnit)運(yùn)行測(cè)試。
    package:打包。打成jar 或 war包,其中會(huì)自動(dòng)進(jìn)行clean+compile。
    verify:檢查。對(duì)集成測(cè)試的結(jié)果進(jìn)行檢查,以保證質(zhì)量達(dá)標(biāo)。
    install:安裝。將本地工程jar包上傳安裝到本地倉(cāng)庫,以供其他項(xiàng)目使用。
    site:站點(diǎn)。進(jìn)行站點(diǎn)部署。
    deploy:部署??截愖罱K的工程包到遠(yuǎn)程倉(cāng)庫或私服中,以共享給其他開發(fā)人員和工程。

 
7、關(guān)于scope依賴范圍

   Maven的生命周期存在編譯、測(cè)試、打包這些過程,其中有些依賴只是用于測(cè)試,如junit,有些依賴編譯時(shí)是用不到的,只有運(yùn)行的時(shí)候才能用到,比如mysql的驅(qū)動(dòng)包在編譯期就用不到(編譯期用的是JDBC接口),而是在運(yùn)行時(shí)用到的。還有些依賴,編譯期要用到,而運(yùn)行期不需要提供,因?yàn)橛行┤萜饕呀?jīng)提供了,比如servlet-api在tomcat中已經(jīng)提供了,我們只需要的是編譯期提供而已。

    其中scope就可以解決上面的問題,即:scope參數(shù)用來控制打包的時(shí)機(jī),scope有如下幾個(gè)值,分別代表如下:

    compile:默認(rèn)的scope,運(yùn)行期有效,需要打入包中。
    provided:編譯期有效,運(yùn)行期不需要提供,不會(huì)打入包中。
    runtime:編譯不需要,在運(yùn)行期有效,需要導(dǎo)入包中。(接口與實(shí)現(xiàn)分離)
    test:測(cè)試需要,不會(huì)打入包中。
    system:非本地倉(cāng)庫引入、存在系統(tǒng)的某個(gè)路徑下的jar。(一般不使用)

 
8、編譯時(shí),出現(xiàn)類似“源值1.5已過時(shí),將在未來所有發(fā)行版中刪除”的錯(cuò)誤

在編譯項(xiàng)目時(shí),如出現(xiàn)如下類似的錯(cuò)誤:

    Warning:java: 源值1.5已過時(shí), 將在未來所有發(fā)行版中刪除
    Warning:java: 目標(biāo)值1.5已過時(shí), 將在未來所有發(fā)行版中刪除
    Warning:java: 要隱藏有關(guān)已過時(shí)選項(xiàng)的警告, 請(qǐng)使用 -Xlint:-options。

    這是由于在IDEA中使用Maven編譯的時(shí)候,項(xiàng)目源和目標(biāo)都使用了JDK 1.5的來編譯,但是目前我們又沒裝JDK1.5(實(shí)際上我們安裝的是JDK1.7以上的版本),最后還是用了我們自己裝的版本來編譯,因此編譯還是不能通過,就出現(xiàn)了剛剛這種錯(cuò)誤!
完整解決方法下面三種:

第一種:將IDEA中對(duì)應(yīng)的項(xiàng)目的:【Modules->Language Level】為 ”8”

    在IDEA中打開項(xiàng)目設(shè)置(或者按下【Ctrl + Alt + Shift + S】)
    找到Modules,找到對(duì)應(yīng)的項(xiàng)目
    將【Language Level】下拉菜單的值改為 "8"

第二種:配置Maven的配置文件,將編譯插件用的JDK改為1.8

    打開settings.xml
    找到 <profiles>...</profiles> 標(biāo)簽對(duì),并在標(biāo)簽對(duì)中間加上如下代碼:

    <profile>
          <id>jdk-1.8</id>
          <activation>
              <activeByDefault>true</activeByDefault>
              <jdk>1.8</jdk>
          </activation>
          <properties>
              <maven.compiler.source>1.8</maven.compiler.source>
              <maven.compiler.target>1.8</maven.compiler.target>
              <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
          </properties>
      </profile>

    啟用該profile設(shè)置,找到 <activeProfiles>...</activeProfiles> 標(biāo)簽對(duì),在中間加入:

  <activeProfile>jdk-1.8</activeProfile>

表示啟用該profile的配置。

第三種:在項(xiàng)目的pom.xml中加入

    <properties>
       <maven.compiler.source>1.8</maven.compiler.source>
       <maven.compiler.target>1.8</maven.compiler.target>
     </properties>

9、報(bào)錯(cuò)“Usage of API documented as @since 1.8+”

  代碼中如果出現(xiàn)下面的錯(cuò)誤提示:

    Usage of API documented as @since 1.8+
    This inspection finds all usages of methods that have @since tag in their documentation.
    This may be useful when development is performed under newer SDK version as the target platform for production.

    出現(xiàn)該問題的原因是由于我們的代碼中使用了JAVA8的新特性,但是Language Level(最低可支持的版本)比較低,無法支持這些特性。比如設(shè)置的Language Level為6.0,可是卻使用了8.0/9.0的新特性,6.0無法解析這些特性,因此IDE會(huì)報(bào)錯(cuò)來提醒我們。

在pom.xml中添加如下配置,就可以解決啦。

    <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.6.0</version>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                    </configuration>
                </plugin>
            </plugins>
    </build>

10、Spring Boot、Spring Cloud項(xiàng)目中如何解決各依賴包間相互沖突的問題?

      在微服務(wù)項(xiàng)目中,我們會(huì)在引入很多依賴包,而且不同依賴包存在很多的版本,經(jīng)常發(fā)現(xiàn)引入的版本不合適,很容易造成一些包中的類相互沖突,如:數(shù)據(jù)庫方面循環(huán)調(diào)用某個(gè)類等,這類問題通常是很難解決的。費(fèi)好大勁,查詢官方網(wǎng)站資料會(huì)發(fā)現(xiàn),是由于依賴的某個(gè)包的版本不對(duì)造成的,你不得不按照要求修改版本號(hào)。

     在此, 我告訴大家一個(gè)方法可以避免這種問題??赏ㄟ^Spring Initializr在線(https://start.spring.io/)創(chuàng)建你的項(xiàng)目,從上面可以選擇你需要的功能模塊,會(huì)自動(dòng)給你匹配對(duì)應(yīng)各個(gè)版本,非常方便。你可以不使用它自動(dòng)生成的demo項(xiàng)目,但可以參考使用它自動(dòng)生成的pom.xml文件,從中獲取對(duì)應(yīng)依賴包的版本。這樣就完全可以避免了因選擇的版本不對(duì),而造成的一些沖突問題。

 

Maven的愛恨情仇,今天就分享到這里,如果你還遇到過其他問題,可以留言一起談?wù)摲窒怼?/p>

    參考:

    1.http://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html

    2.https://www.runoob.com/maven/maven-build-life-cycle.html

    3.https://www.cnblogs.com/wangyonghao/p/5976055.html