Maven的愛恨情仇

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

前言

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

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

 
為什么要使用Maven

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

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

    如果一個項目中依賴的版本和另一個項目依賴的版本不一致。比如這個項目依賴spring-boot-starter-parent-1.5.9.RELEASE,而另一個可能依賴spring-boot-starter-parent-2.0.0.RELEASE, 當合并兩個項目發(fā)布的時候,可能因為這種依賴類庫詳細版本信息的缺失,造成版本混亂沖突等問題。

3、管理問題

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

 

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

 

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

 

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

 

本地倉庫路徑配置:

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

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

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

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

 
2、關于<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,就可以在倉庫中查找到依賴jar的位置。

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

 

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

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

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

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

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

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

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






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

 
5、Maven規(guī)范化目錄結構

上圖為Maven項目的規(guī)范目錄結構。

需要有下面兩點注意:

① src/main下內容最終會打包到Jar/War中,而src/test下是測試內容,并不會打包進去。

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

 
6、Maven的生命周期

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

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

 
7、關于scope依賴范圍

   Maven的生命周期存在編譯、測試、打包這些過程,其中有些依賴只是用于測試,如junit,有些依賴編譯時是用不到的,只有運行的時候才能用到,比如mysql的驅動包在編譯期就用不到(編譯期用的是JDBC接口),而是在運行時用到的。還有些依賴,編譯期要用到,而運行期不需要提供,因為有些容器已經(jīng)提供了,比如servlet-api在tomcat中已經(jīng)提供了,我們只需要的是編譯期提供而已。

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

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

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

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

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

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

第一種:將IDEA中對應的項目的:【Modules->Language Level】為 ”8”

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

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

    打開settings.xml
    找到 <profiles>...</profiles> 標簽對,并在標簽對中間加上如下代碼:

    <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設置,找到 <activeProfiles>...</activeProfiles> 標簽對,在中間加入:

  <activeProfile>jdk-1.8</activeProfile>

表示啟用該profile的配置。

第三種:在項目的pom.xml中加入

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

9、報錯“Usage of API documented as @since 1.8+”

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

    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(最低可支持的版本)比較低,無法支持這些特性。比如設置的Language Level為6.0,可是卻使用了8.0/9.0的新特性,6.0無法解析這些特性,因此IDE會報錯來提醒我們。

在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項目中如何解決各依賴包間相互沖突的問題?

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

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

 

Maven的愛恨情仇,今天就分享到這里,如果你還遇到過其他問題,可以留言一起談論分享。

    參考:

    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