在如今的互聯(lián)網(wǎng)項(xiàng)目開發(fā)當(dāng)中暂筝,特別是Java領(lǐng)域箩言,可以說(shuō)Maven隨處可見。Maven的倉(cāng)庫(kù)管理焕襟、依賴管理陨收、繼承和聚合等特性為項(xiàng)目的構(gòu)建提供了一整套完善的解決方案,可以說(shuō)如果你搞不懂Maven,那么一個(gè)多模塊的項(xiàng)目足以讓你頭疼务漩,依賴沖突就會(huì)讓你不知所措拄衰,甚至搞不清楚項(xiàng)目是如何運(yùn)行起來(lái)的....
OK,博主就曾經(jīng)被Maven“傷害”過(guò)饵骨,那么該專題的目的就是:徹底搞定Maven翘悉!
回想一下,當(dāng)你新到一家公司居触,安裝完JDK后就會(huì)安裝配置Maven(MAVEN_HOME妖混、path),很大可能性你需要修改settings.xml文件饼煞,比如你會(huì)修改本地倉(cāng)庫(kù)地址路徑源葫,比如你很可能會(huì)copy一段配置到你的settings.xml中(很可能就是私服的一些配置)。
接下來(lái)砖瞧,你會(huì)到IDEA或者Eclipse中進(jìn)行Maven插件配置息堂,然后你就可以在工程中的pom.xml里面開始添加<dependency>標(biāo)簽來(lái)管理jar包,在Maven規(guī)范的目錄結(jié)構(gòu)下進(jìn)行編寫代碼块促,最后你會(huì)通過(guò)插件的方式來(lái)進(jìn)行測(cè)試荣堰、打包(jar or war)、部署竭翠、運(yùn)行振坚。
上面描述了我們對(duì)Maven的一些使用方式,下面我們進(jìn)行一些思考:
1. 本地倉(cāng)庫(kù)斋扰?Maven到底有哪些倉(cāng)庫(kù)渡八?它們什么關(guān)系?
Maven倉(cāng)庫(kù)
本地倉(cāng)庫(kù)路徑配置
你要jar包传货,不可能每次都要聯(lián)網(wǎng)去下載吧屎鳍,多費(fèi)勁,所以本地倉(cāng)庫(kù)就是相當(dāng)于加了一層jar包緩存问裕,先到這里來(lái)查逮壁。如果這里查不到,那么就去私服上找粮宛,如果私服也找不到窥淆,那么去中央倉(cāng)庫(kù)去找,找到j(luò)ar后巍杈,會(huì)把jar的信息同步到私服和本地倉(cāng)庫(kù)中忧饭。
私服,就是公司內(nèi)部局域網(wǎng)的一臺(tái)服務(wù)器而已秉氧,你想一下眷昆,當(dāng)你的工程Project-A依賴別人的Project-B的接口,怎么做呢?沒(méi)有Maven的時(shí)候亚斋,當(dāng)然是copy Project-B jar到你的本地lib中引入作媚,那么Maven的方式,很顯然需要其他人把Project-B deploy到私服倉(cāng)庫(kù)中供你使用帅刊。因此私服中存儲(chǔ)了本公司的內(nèi)部專用的jar纸泡!不僅如此,私服還充當(dāng)了中央倉(cāng)庫(kù)的鏡像赖瞒,說(shuō)白了就是一個(gè)代理女揭!如何創(chuàng)建私服,可以關(guān)注Java技術(shù)棧微信公眾號(hào)栏饮,回復(fù):maven吧兔,即可獲取。
中央倉(cāng)庫(kù):該倉(cāng)庫(kù)存儲(chǔ)了互聯(lián)網(wǎng)上的jar袍嬉,由Maven團(tuán)隊(duì)來(lái)維護(hù)境蔼,地址是:http://repo1.maven.org/maven2/。
2. 關(guān)于<dependency>的使用
依賴管理
其實(shí)這個(gè)標(biāo)簽揭示了jar的查找坐標(biāo):groupId伺通、artifactId箍土、version。
一般而言罐监,我們可以到私服上輸入artifactId進(jìn)行搜索吴藻,或者到http://search.maven.org/、http://mvnrepository.com/上進(jìn)行查找確定坐標(biāo)弓柱。
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í)。過(guò)了幾天后绢彤,B又發(fā)現(xiàn)了問(wèn)題七问,進(jìn)行修改后升級(jí)版本發(fā)布,然后通知A進(jìn)行升級(jí)...可以說(shuō)這是開發(fā)過(guò)程中的版本不穩(wěn)定導(dǎo)致了這樣的問(wèn)題茫舶。
Maven械巡,已經(jīng)替我們想好了解決方案,就是使用Snapshot版本,在開發(fā)過(guò)程中B發(fā)布的版本標(biāo)志為Snapshot版本讥耗,A進(jìn)行依賴的時(shí)候選擇Snapshot版本有勾,那么每次B發(fā)布的話,會(huì)在私服倉(cāng)庫(kù)中古程,形成帶有時(shí)間戳的Snapshot版本蔼卡,而A構(gòu)建的時(shí)候會(huì)自動(dòng)下載B最新時(shí)間戳的Snapshot版本!
3. 既然Maven進(jìn)行了依賴管理挣磨,為什么還會(huì)出現(xiàn)依賴沖突雇逞?處理依賴沖突的手段是?
依賴的版本茁裙?
首先來(lái)說(shuō)塘砸,對(duì)于Maven而言,同一個(gè)groupId同一個(gè)artifactId下晤锥,只能使用一個(gè)version掉蔬!
根據(jù)上圖的依賴順序,將使用1.2版本的jar查近。
現(xiàn)在眉踱,我們可以思考下了,比如工程中需要引入A霜威、B谈喳,而A依賴1.0版本的C,B依賴2.0版本的C戈泼,那么問(wèn)題來(lái)了婿禽,C使用的版本將由引入A、B的順序而定大猛?這顯然不靠譜扭倾!如果A的依賴寫在B的依賴后面,將意味著最后引入的是1.0版本的C挽绩,很可能在運(yùn)行階段出現(xiàn)類(ClassNotFoundException)膛壹、方法(NoSuchMethodError)找不到的錯(cuò)誤(因?yàn)锽使用的是高版本的C)!
這里其實(shí)涉及到了2個(gè)概念:依賴傳遞(transitive)唉堪、Maven的最近依賴策略模聋。
依賴傳遞:如果A依賴B,B依賴C唠亚,那么引入A链方,意味著B和C都會(huì)被引入。
Maven的最近依賴策略:如果一個(gè)項(xiàng)目依賴相同的groupId灶搜、artifactId的多個(gè)版本祟蚀,那么在依賴樹(mvn dependency:tree)中離項(xiàng)目最近的那個(gè)版本將會(huì)被使用工窍。(從這里可以看出Maven是不是有點(diǎn)小問(wèn)題呢?能不能選擇高版本的進(jìn)行依賴么前酿?據(jù)了解患雏,Gradle就是version+策略)
現(xiàn)在,我們可以想想如何處理依賴沖突呢薪者?
想法1:要使用哪個(gè)版本纵苛,我們是清楚的,那么能不能不管如何依賴傳遞言津,都可以進(jìn)行版本鎖定呢攻人?
使用<dependencyManagement> [這種主要用于子模塊的版本一致性中]
想法2:在依賴傳遞中,能不能去掉我們不想依賴的悬槽?
使用<exclusions> [在實(shí)際中我們可以在IDEA中直接利用插件幫助我們生成]
想法3:既然是最近依賴策略怀吻,那么我們就直接使用顯式依賴指定版本,那不就是最靠近項(xiàng)目的么初婆?Maven繼承與聚合蓬坡,這個(gè)你需要掌握。
使用<dependency>
4. 引入依賴的最佳實(shí)踐磅叛,提前發(fā)現(xiàn)問(wèn)題屑咳!
在工程中,我們避免不了需要加一些依賴弊琴,也許加了依賴后運(yùn)行時(shí)才發(fā)現(xiàn)存在依賴沖突在去解決兆龙,似乎有點(diǎn)晚!那么能不能提前發(fā)現(xiàn)問(wèn)題呢敲董?
如果我們新加入一個(gè)依賴的話紫皇,那么先通過(guò)mvn dependency:tree命令形成依賴樹,看看我們新加入的依賴腋寨,是否存在傳遞依賴聪铺,傳遞依賴中是否和依賴樹中的版本存在沖突,如果存在多個(gè)版本沖突萄窜,利用上文的方式進(jìn)行解決铃剔!
5. Maven規(guī)范化目錄結(jié)構(gòu)
簡(jiǎn)單Java工程目錄結(jié)構(gòu)
這里需要注意2點(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生命周期
我們只需要注意一點(diǎn):執(zhí)行后面的命令時(shí)火欧,前面的命令自動(dòng)得到執(zhí)行棋电。
實(shí)際上,我們最常用的就是這么幾個(gè):
clean:有問(wèn)題苇侵,多清理赶盔!
package:打成Jar or War包,會(huì)自動(dòng)進(jìn)行clean+compile
install:將本地工程Jar上傳到本地倉(cāng)庫(kù)
deploy:上傳到私服
7. 關(guān)于scope依賴范圍
既然榆浓,Maven的生命周期存在編譯于未、測(cè)試、運(yùn)行這些過(guò)程陡鹃,那么顯然有些依賴只用于測(cè)試烘浦,比如junit;有些依賴編譯用不到萍鲸,只有運(yùn)行的時(shí)候才能用到闷叉,比如mysql的驅(qū)動(dòng)包在編譯期就用不到(編譯期用的是JDBC接口),而是在運(yùn)行時(shí)用到的脊阴;還有些依賴握侧,編譯期要用到,而運(yùn)行期不需要提供嘿期,因?yàn)橛行┤萜饕呀?jīng)提供了品擎,比如servlet-api在tomcat中已經(jīng)提供了,我們只需要的是編譯期提供而已备徐。
總結(jié)來(lái)說(shuō):
compile:默認(rèn)的scope萄传,運(yùn)行期有效,需要打入包中坦喘。
provided:編譯期有效盲再,運(yùn)行期不需要提供,不會(huì)打入包中瓣铣。
runtime:編譯不需要答朋,在運(yùn)行期有效,需要導(dǎo)入包中棠笑。(接口與實(shí)現(xiàn)分離)
test:測(cè)試需要梦碗,不會(huì)打入包中。
system:非本地倉(cāng)庫(kù)引入蓖救、存在系統(tǒng)的某個(gè)路徑下的jar洪规。(一般不使用)