面試官:Maven 的這 7 個問題你思考過沒有?
你知道的越多,不知道的就越多,業(yè)余的像一棵小草!
你來,我們一起精進!你不來,我和你的競爭對手一起精進!
編輯:業(yè)余草
dwz.cn/PHYFFK27
推薦:https://www.xttblog.com/?p=5269
現(xiàn)在的項目中 Maven 隨處可見,面試的時候,經(jīng)常會被問一些項目中 Maven 的問題,但是平時 Maven 項目一般不會出什么問題,可能你不太注意,以下7個問題,一般說出來并掌握,至少可以證明你 Maven 用的熟練度還可以。
「Maven的倉庫管理、依賴管理、繼承和聚合等特性為項目的構(gòu)建提供了一整套完善的解決方案,如果你搞不懂Maven,那么一個多模塊的項目足以讓你頭疼,依賴沖突就會讓你不知所措,甚至搞不清楚項目是如何運行起來的...」
OK,博主就曾經(jīng)被 Maven“傷害”過,所以,我們要:「徹底搞定Maven!」
回想一下,當你新到一家公司,安裝完 JDK 后就會安裝配置 Maven,很大可能性你需要修改 settings.xml 文件,比如你會修改本地倉庫地址路徑,比如你很可能會 copy 一段配置到你的 settings.xml 中(很可能就是私服的一些配置)。接下來,你會到 IDEA 或者 Eclipse 中進行 Maven 插件配置,然后你就可以在工程中的 pom.xml 里面開始添加標簽來管理 jar 包,在 Maven 規(guī)范的目錄結(jié)構(gòu)下進行編寫代碼,最后你會通過插件的方式來進行測試、打包(jar or war)、部署、運行。
上面描述了對 Maven 的一些使用方式,下面我們進行一些思考:
1、本地倉庫?Maven 到底有哪些倉庫?它們什么關(guān)系?
Maven倉庫:

本地倉庫路徑配置:

你要 jar 包,不可能每次都要聯(lián)網(wǎng)去下載吧,多費勁,所以本地倉庫就是相當于加了一層 jar 包緩存,先到這里來查。如果這里查不到,那么就去私服上找,如果私服也找不到,那么去中央倉庫去找,找到 jar 后,會把 jar 的信息同步到私服和本地倉庫中。
「私服」,就是公司內(nèi)部局域網(wǎng)的一臺服務(wù)器而已,你想一下,當你的工程 Project-A 依賴別人的 Project-B 的接口,怎么做呢?沒有 Maven 的時候,當然是 copy Project-B jar 到你的本地 lib 中引入,那么 Maven 的方式,很顯然需要其他人把 Project-B deploy 到私服倉庫中供你使用。「因此私服中存儲了本公司的內(nèi)部專用的 jar!不僅如此,私服還充當了中央倉庫的鏡像,說白了就是一個代理!」
「中央倉庫」:該倉庫存儲了互聯(lián)網(wǎng)上的 jar,由 Maven 團隊來維護,地址是:http://repo1.maven.org/maven2/。
2、關(guān)于<dependency>的使用
「依賴管理:」


其實這個標簽揭示了 jar 的查找坐標:「groupId」、「artifactId」、「version」。
一般而言,我們可以到私服上輸入 artifactId 進行搜索,或者到http://search.maven.org/、http://mvnrepository.com/上進行查找確定坐標。
「version分為開發(fā)版本(Snapshot)和發(fā)布版本(Release),那么為什么要分呢?」
在實際開發(fā)中,我們經(jīng)常遇到這樣的場景,比如A服務(wù)依賴于 B 服務(wù),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)定導(dǎo)致了這樣的問題。
「Maven,已經(jīng)替我們想好了解決方案,就是使用 Snapshot 版本,在開發(fā)過程中 B 發(fā)布的版本標志為 Snapshot 版本,A 進行依賴的時候選擇 Snapshot 版本,那么每次B發(fā)布的話,會在私服倉庫中,形成帶有時間戳的 Snapshot 版本,而 A 構(gòu)建的時候會自動下載 B 最新時間戳的 Snapshot 版本!」
3、既然 Maven 進行了依賴管理,為什么還會出現(xiàn)依賴沖突?處理依賴沖突的手段是?
依賴的版本?

首先來說,「對于 Maven 而言,同一個 groupId 同一個 artifactId 下,只能使用一個 version」!
根據(jù)上圖的依賴順序,將使用 1.2 版本的 jar。
現(xiàn)在,我們可以思考下了,比如工程中需要引入 A、B,而 A 依賴 1.0 版本的 C,B 依賴 2.0 版本的 C,那么問題來了,C 使用的版本將由引入 A、B 的順序而定?這顯然不靠譜!如果A的依賴寫在 B 的依賴后面,將意味著最后引入的是 1.0 版本的 C,很可能在運行階段出現(xiàn)類(「ClassNotFoundException」)、方法(「NoSuchMethodError」)找不到的錯誤(因為B使用的是高版本的 C)!
「這里其實涉及到了 2 個概念:依賴傳遞(transitive)、Maven的最近依賴策略?!?/strong>
「依賴傳遞:如果 A 依賴 B,B 依賴 C,那么引入 A,意味著 B 和 C 都會被引入?!?/strong>
「Maven 的最近依賴策略:如果一個項目依賴相同的 groupId、artifactId 的多個版本,那么在依賴樹(mvn dependency:tree)中離項目最近的那個版本將會被使用。(從這里可以看出 Maven 是不是有點小問題呢?能不能選擇高版本的進行依賴么?據(jù)了解,Gradle 就是 version + 策略)」
現(xiàn)在,我們可以想想如何處理依賴沖突呢?
想法1:要使用哪個版本,我們是清楚的,那么能不能不管如何依賴傳遞,都可以進行版本鎖定呢?
「使用<dependencyManagement> 這種主要用于子模塊的版本一致性中」
想法2:在依賴傳遞中,能不能去掉我們不想依賴的?
「使用<exclusions>在實際中我們可以在 IDEA 中直接利用插件幫助我們生成」
想法3:既然是最近依賴策略,那么我們就直接使用顯式依賴指定版本,那不就是最靠近項目的么?
「使用<dependency>」
4、引入依賴的最佳實踐,提前發(fā)現(xiàn)問題!
在工程中,我們避免不了需要加一些依賴,也許加了依賴后運行時才發(fā)現(xiàn)存在依賴沖突在去解決,似乎有點晚!那么能不能提前發(fā)現(xiàn)問題呢?
「如果我們新加入一個依賴的話,那么先通過 mvn dependency:tree 命令形成依賴樹,看看我們新加入的依賴,是否存在傳遞依賴,傳遞依賴中是否和依賴樹中的版本存在沖突,如果存在多個版本沖突,利用上文的方式進行解決!」
5、Maven規(guī)范化目錄結(jié)構(gòu)

「這里需要注意2點:」
1、src/main 下內(nèi)容最終會打包到 Jar/War 中,而 src/test 下是測試內(nèi)容,并不會打包進去。
2、src/main/resources 中的資源文件會 COPY 至目標目錄,這是 Maven 的默認生命周期中的一個規(guī)定動作。(想一想,hibernate/mybatis 的映射 XML 需要放入 resources 下,而不能在放在其他地方了)
6、Maven 的生命周期
「Maven生命周期:」

我們只需要注意一點:「執(zhí)行后面的命令時,前面的命令自動得到執(zhí)行?!?/strong>
「實際上,我們最常用的就是這么幾個:」
1、clean:有問題,多清理!2、package:打成 Jar or War 包,會自動進行 clean + compile 3、install:將本地工程 Jar 上傳到本地倉庫 4、deploy:上傳到私服
7、關(guān)于 scope 依賴范圍
既然,Maven 的生命周期存在編譯、測試、運行這些過程,那么顯然有些依賴只用于測試,比如「junit」;有些依賴編譯用不到,只有運行的時候才能用到,比如** mysql 的驅(qū)動包「在編譯期就用不到(「編譯期用的是 JDBC 接口」),而是在運行時用到的;還有些依賴,編譯期要用到,而運行期不需要提供,因為有些容器已經(jīng)提供了,比如」servlet-api**在 tomcat 中已經(jīng)提供了,我們只需要的是編譯期提供而已。
總結(jié)來說:
1、compile:默認的 scope,運行期有效,需要打入包中。
2、provided:編譯期有效,運行期不需要提供,不會打入包中。
3、runtime:編譯不需要,在運行期有效,需要導(dǎo)入包中。(接口與實現(xiàn)分離)
4、test:測試需要,不會打入包中。
5、system:非本地倉庫引入、存在系統(tǒng)的某個路徑下的 jar。(一般不使用)
