(給ImportNew加星標,提高Java技能)
作者:張豐哲,
www.jianshu.com/p/20b39ab6a88c
前言
在如今的網際網路專案開發當中,特別是Java領域,可以說Maven隨處可見。Maven的倉庫管理、依賴管理、繼承和聚合等特性為專案的構建提供了一整套完善的解決方案,可以說如果你搞不懂Maven,那麼一個多模組的專案足以讓你頭疼,依賴衝突就會讓你不知所措,甚至搞不清楚專案是如何執行起來的…..OK,博主就曾經被Maven“傷害”過,那麼該專題的目的就是:徹底搞定Maven!
Thinking in Maven
回想一下,當你新到一家公司,安裝完JDK後就會安裝配置Maven(MAVEN_HOME、path),很大可能性你需要修改settings.xml檔案,比如你會修改本地倉庫地址路徑,比如你很可能會copy一段配置到你的settings.xml中(很可能就是私服的一些配置)。接下來,你會到IDEA或者Eclipse中進行Maven外掛配置,然後你就可以在工程中的pom.xml裡面開始新增標簽來管理jar包,在Maven規範的目錄結構下進行編寫程式碼,最後你會透過外掛的方式來進行測試、打包(jar or war)、部署、執行。
上面描述了我們對Maven的一些使用方式,下麵我們進行一些思考:
Q1:本地倉庫?Maven到底有哪些倉庫?它們什麼關係?
Maven倉庫
本地倉庫路徑配置
你要jar包,不可能每次都要聯網去下載吧,多費勁,所以本地倉庫就是相當於加了一層jar包快取,先到這裡來查。如果這裡查不到,那麼就去私服上找,如果私服也找不到,那麼去中央倉庫去找,找到jar後,會把jar的資訊同步到私服和本地倉庫中。
私服,就是公司內部區域網的一臺伺服器而已,你想一下,當你的工程Project-A依賴別人的Project-B的介面,怎麼做呢?沒有Maven的時候,當然是copy Project-B jar到你的本地lib中引入,那麼Maven的方式,很顯然需要其他人把Project-B deploy到私服倉庫中供你使用。因此私服中儲存了本公司的內部專用的jar!不僅如此,私服還充當了中央倉庫的映象,說白了就是一個代理!
中央倉庫:該倉庫儲存了網際網路上的jar,由Maven團隊來維護,地址是:http://repo1.maven.org/maven2/。
Q2:關於的使用
依賴管理
其實這個標簽揭示了jar的查詢坐標:groupId、artifactId、version。
一般而言,我們可以到私服上輸入artifactId進行搜尋,或者到http://search.maven.org/、http://mvnrepository.com/上進行查詢確定坐標。
version分為開發版本(Snapshot)和釋出版本(Release),那麼為什麼要分呢?
在實際開發中,我們經常遇到這樣的場景,比如A服務依賴於B服務,A和B同時開發,B在開發中發現了BUG,修改後,將版本由1.0升級為2.0,那麼A必須也跟著在POM.XML中進行版本升級。過了幾天后,B又發現了問題,進行修改後升級版本釋出,然後通知A進行升級…可以說這是開發過程中的版本不穩定導致了這樣的問題。
Maven,已經替我們想好瞭解決方案,就是使用Snapshot版本,在開發過程中B釋出的版本標誌為Snapshot版本,A進行依賴的時候選擇Snapshot版本,那麼每次B釋出的話,會在私服倉庫中,形成帶有時間戳的Snapshot版本,而A構建的時候會自動下載B最新時間戳的Snapshot版本!
Q3:既然Maven進行了依賴管理,為什麼還會出現依賴衝突?處理依賴衝突的手段是?
依賴的版本?
首先來說,對於Maven而言,同一個groupId同一個artifactId下,只能使用一個version!
根據上圖的依賴順序,將使用1.2版本的jar。
現在,我們可以思考下了,比如工程中需要引入A、B,而A依賴1.0版本的C,B依賴2.0版本的C,那麼問題來了,C使用的版本將由引入A、B的順序而定?這顯然不靠譜!如果A的依賴寫在B的依賴後面,將意味著最後引入的是1.0版本的C,很可能在執行階段出現類(ClassNotFoundException)、方法(NoSuchMethodError)找不到的錯誤(因為B使用的是高版本的C)!
這裡其實涉及到了2個概念:依賴傳遞(transitive)、Maven的最近依賴策略。
依賴傳遞:如果A依賴B,B依賴C,那麼引入A,意味著B和C都會被引入。
Maven的最近依賴策略:如果一個專案依賴相同的groupId、artifactId的多個版本,那麼在依賴樹(mvn dependency:tree)中離專案最近的那個版本將會被使用。(從這裡可以看出Maven是不是有點小問題呢?能不能選擇高版本的進行依賴麼?據瞭解,Gradle就是version+策略)
現在,我們可以想想如何處理依賴衝突呢?
想法1:要使用哪個版本,我們是清楚的,那麼能不能不管如何依賴傳遞,都可以進行版本鎖定呢?
使用 [這種主要用於子模組的版本一致性中]
想法2:在依賴傳遞中,能不能去掉我們不想依賴的?
使用 [在實際中我們可以在IDEA中直接利用外掛幫助我們生成]
想法3:既然是最近依賴策略,那麼我們就直接使用顯式依賴指定版本,那不就是最靠近專案的麼?
使用
Q4:引入依賴的最佳實踐,提前發現問題!
在工程中,我們避免不了需要加一些依賴,也許加了依賴後執行時才發現存在依賴衝突在去解決,似乎有點晚!那麼能不能提前發現問題呢?
如果我們新加入一個依賴的話,那麼先透過mvn dependency:tree命令形成依賴樹,看看我們新加入的依賴,是否存在傳遞依賴,傳遞依賴中是否和依賴樹中的版本存在衝突,如果存在多個版本衝突,利用上文的方式進行解決!
Q5:Maven規範化目錄結構
簡單Java工程目錄結構
這裡需要註意2點:
第一:src/main下內容最終會打包到Jar/War中,而src/test下是測試內容,並不會打包進去。
第二:src/main/resources中的資源檔案會COPY至標的目錄,這是Maven的預設生命週期中的一個規定動作。(想一想,hibernate/mybatis的對映XML需要放入resources下,而不能在放在其他地方了)
Q6:Maven的生命週期
Maven生命週期
我們只需要註意一點:執行後面的命令時,前面的命令自動得到執行。
實際上,我們最常用的就是這麼幾個:
clean:有問題,多清理!
package:打成Jar or War包,會自動進行clean+compile
install:將本地工程Jar上傳到本地倉庫
deploy:上傳到私服
Q7:關於scope依賴範圍
既然,Maven的生命週期存在編譯、測試、執行這些過程,那麼顯然有些依賴只用於測試,比如junit;有些依賴編譯用不到,只有執行的時候才能用到,比如mysql的驅動包在編譯期就用不到(編譯期用的是JDBC介面),而是在執行時用到的;還有些依賴,編譯期要用到,而執行期不需要提供,因為有些容器已經提供了,比如servlet-api在tomcat中已經提供了,我們只需要的是編譯期提供而已。
總結來說:
compile:預設的scope,執行期有效,需要打入包中。
provided:編譯期有效,執行期不需要提供,不會打入包中。
runtime:編譯不需要,在執行期有效,需要匯入包中。(介面與實現分離)
test:測試需要,不會打入包中。
system:非本地倉庫引入、存在系統的某個路徑下的jar。(一般不使用)
Maven虐我千百遍,我待Maven如初戀!
朋友會在“發現-看一看”看到你“在看”的內容