眾所周知,Java包管理是學(xué)習(xí)Java過(guò)程中一個(gè)很重要的內(nèi)容温圆。本篇文章將著重介紹Java包管理以及Maven包管理的原理麻敌,以及解決包沖突的方法。
JVM工作原理
首先帘不,我們先來(lái)了解一下JVM的工作原理说莫,其實(shí)簡(jiǎn)單地概括性地說(shuō),JVM只會(huì)做兩件事情:
- 執(zhí)行一個(gè)類(lèi)的字節(jié)碼
- 在執(zhí)行這個(gè)類(lèi)的字節(jié)碼的時(shí)候寞焙,若碰到了新的類(lèi)储狭,則加載它
- 不斷重復(fù)以上兩個(gè)過(guò)程
可見(jiàn)互婿,JVM的工作是如此的簡(jiǎn)單和枯燥,但讀到這里的你辽狈,可能會(huì)產(chǎn)生一個(gè)問(wèn)題慈参,JVM是怎么知道在哪里讀取這些類(lèi)的呢?
針對(duì)這個(gè)問(wèn)題刮萌,我也不賣(mài)關(guān)子了懂牧,直接告訴你答案,JVM是通過(guò)classpath
參數(shù)來(lái)獲取到這個(gè)路徑的尊勿。
那么新的問(wèn)題又出現(xiàn)了僧凤,我明明沒(méi)有給JVM傳遞這個(gè)參數(shù)呀,它是怎么獲取到的呢元扔?
是的躯保,沒(méi)錯(cuò),你沒(méi)有給JVM傳遞classpath
這個(gè)參數(shù)澎语,但是你的編譯器偷偷幫你干了這件事情了途事!
(不相信的話(huà),每次用編譯器編譯的時(shí)候擅羞,控制臺(tái)都會(huì)有一串命令尸变,在命令里面你可以清楚地看到編譯器給JVM偷偷傳遞了classpath
參數(shù))
另外,由于一個(gè)包有可能又依賴(lài)于其他很多個(gè)包减俏,因此一個(gè)項(xiàng)目下來(lái)召烂,可能classpath
下的依賴(lài)路徑會(huì)變得又臭又長(zhǎng)。
在Java剛誕生的時(shí)候娃承,人們是需要通過(guò)手寫(xiě)這些classpath
路徑來(lái)讓JVM讀懂讀取jar包(一堆類(lèi)的集合)的路徑的奏夫,后面再人們的不斷努力下,強(qiáng)大的IDEA和Maven的誕生历筝,才讓這個(gè)繁瑣的過(guò)程變得無(wú)比簡(jiǎn)單酗昼。
依賴(lài)地獄
在Maven誕生之前,依賴(lài)沖突是一個(gè)很容易發(fā)生且很難解決的問(wèn)題梳猪,我們把這種依賴(lài)沖突又稱(chēng)為classpath hell
(依賴(lài)地獄)麻削。
什么是依賴(lài)沖突,由于全限定類(lèi)名是類(lèi)的唯一標(biāo)示春弥,因此當(dāng)多個(gè)同名類(lèi)不同版本同時(shí)出現(xiàn)在classpath的時(shí)候呛哟,就是噩夢(mèng)的開(kāi)始。
如上圖惕稻,A包依賴(lài)了B包和C2包竖共,而B(niǎo)包又依賴(lài)了C1包,在這個(gè)時(shí)候俺祠,由于所有的依賴(lài)包的路徑都會(huì)寫(xiě)在
classpath
上面公给,讓JVM從前往后地在這些路徑上面尋早需要的依賴(lài)包借帘,因此,若JVM先讀取到了C1依賴(lài)包的classpath
路徑淌铐,那么C2這個(gè)依賴(lài)包肺然,由于和C1只是版本上面的不同,因此JVM會(huì)誤把C1路徑中找到的依賴(lài)包也同樣作用在C2上面腿准,從而導(dǎo)致出現(xiàn)不可預(yù)期的錯(cuò)誤际起。
一般來(lái)說(shuō),當(dāng)你看到你的代碼在編譯運(yùn)行之后吐葱,出現(xiàn)了以下的錯(cuò)誤街望,那就代表最麻煩的包沖突出現(xiàn)了:
- AbstractMethodError
- NoClassDefFoundError
- ClassNotFoundException
- LinkageError
Maven包管理的原理
在Maven沒(méi)有誕生之前,包沖突只能通過(guò)手動(dòng)尋找沖突的包依賴(lài)弟跑,并把對(duì)應(yīng)的包進(jìn)行升級(jí)或者替換灾前,但問(wèn)題是,一個(gè)項(xiàng)目一般存在著很多很多的包依賴(lài)孟辑,手動(dòng)尋找費(fèi)時(shí)費(fèi)力哎甲,效率太低。直到后來(lái)Maven的誕生饲嗽,才使得解決包依賴(lài)的解決變得不再那么麻煩炭玫。
首先我們先來(lái)了解一下Maven是如何對(duì)包進(jìn)行管理的
我們需要首先知道的是,Maven有一套約定俗成的規(guī)范貌虾,其中規(guī)定了吞加,生產(chǎn)代碼需要放在src/main
目錄下面,而測(cè)試代碼則需要放在test/main
目錄下面酝惧。這個(gè)將在之后講包管理的scope中有用榴鼎。
Maven會(huì)有中央倉(cāng)庫(kù)和本地倉(cāng)庫(kù)兩個(gè)倉(cāng)庫(kù)
本地倉(cāng)庫(kù)即字面意思在本地你電腦中存在的倉(cāng)庫(kù),它默認(rèn)位于~/.m2
目錄中晚唇,里面會(huì)放置一些經(jīng)過(guò)下載的第三方包的緩存。
而中央倉(cāng)庫(kù)即線(xiàn)上倉(cāng)庫(kù)的意思盗似,一個(gè)包會(huì)含有groupId
哩陕、artifactId
、version
三個(gè)字段赫舒,因此在中央倉(cāng)庫(kù)中悍及,一個(gè)包存放的路徑也是以這三個(gè)字段來(lái)存放的,具體會(huì)存放在groupId/artifactId/version
這個(gè)位置接癌。
當(dāng)一個(gè)項(xiàng)目需要使用一些第三方包的時(shí)候心赶,你可以在pom文件中添加這些包的信息,這樣Maven就會(huì)自動(dòng)幫你下載這些包以及其相關(guān)依賴(lài)包到本地中緩存起來(lái)缺猛,具體的添加方式如下圖所示:
如何解決包沖突
好了缨叫,說(shuō)到這里椭符,相信你已經(jīng)基本了解了Maven是如何對(duì)第三方包進(jìn)行管理的了,接下來(lái)耻姥,我們就來(lái)講一下Maven是如何解決包沖突的销钝。
首先我們需要知道解決包沖突的一個(gè)原則:絕對(duì)不允許最終的classpath
出現(xiàn)同名不同版本的jar包
相比于C1來(lái)說(shuō)婉商,C2這個(gè)第三方包離項(xiàng)目更接近似忧,因此Maven會(huì)自動(dòng)幫你把C1去除,而保留C2丈秩。但是這種策略有時(shí)候是不完美的盯捌,因此有時(shí)候也需要我們?nèi)藶榈厝ゾS護(hù)它,但不管怎么說(shuō)癣籽,由于Maven的誕生挽唉,使得我們對(duì)于第三方包的很多操作都變得輕松和簡(jiǎn)單了。
接下來(lái)我們來(lái)說(shuō)一下人為解決沖突的三種辦法:
- 直接依賴(lài)高版本依賴(lài)筷狼,這樣Maven就能去除所有低版本的不合適的依賴(lài)了瓶籽,具體來(lái)說(shuō)如下所示:
C1、C2和C3三個(gè)版本沖突了埂材,但我們只要直接依賴(lài)了最高版本C3塑顺,把它作為項(xiàng)目的直接依賴(lài),這樣C1俏险、C2這兩個(gè)不合適的第三方包就會(huì)自動(dòng)被Maven去除掉严拒,從而解決了沖突。
- 通過(guò)pom文件來(lái)排除包中的后代指定依賴(lài)
具體操作如以下代碼所示:
<dependency>
<groupId>xxx</groupId>
<artifactId>xxx</artifactId>
<version>1.0.0</version>
<exclusions>
<exclusion>
<groupId>yyy</groupId>
<artifactId>yyy</artifactId>
</exclusion>
</exclusions>
</dependency>
通過(guò)以上的代碼竖独,即可排除了xxx依賴(lài)中的后代yyy依賴(lài)裤唠,也可以解決包沖突的問(wèn)題
3.通過(guò)Maven helper插件來(lái)解決包沖突問(wèn)題,由于這個(gè)是工具性的操作莹痢,因此這里不過(guò)多介紹种蘸,大家可以自行去嘗試。
最后竞膳,說(shuō)一下pom文件中航瞭,可以通過(guò)設(shè)置scope標(biāo)簽,來(lái)指定一個(gè)包是否可以被生產(chǎn)代碼和測(cè)試代碼所引用:
- complie(生產(chǎn)代碼以及測(cè)試代碼均可見(jiàn))
- test(只有測(cè)試代碼可見(jiàn))
- provided(只在編譯的生產(chǎn)代碼的時(shí)候生效坦辟,在運(yùn)行時(shí)無(wú)效)
以上刊侯,就是本篇文章的所有內(nèi)容,謝謝閱讀~