Maven的依賴管理
Maven中的依賴scope
scope定義了依賴的項目在編譯椎组、測試、打包三個不同階段是否生效,下面列舉了三個階段中依賴是否生效的具體表現(xiàn)店枣,以加深理解:
首先是編譯階段,這個階段將用戶的java文件編譯成class文件灌闺,如果依賴在這個階段不生效艰争,那么當用戶的主代碼中使用了該依賴中的類時,會造成編譯失敗桂对。具體現(xiàn)象是mvn compile失敗甩卓,或者更直觀的,在ide中提示找不到類蕉斜,編譯錯誤逾柿。
然后是測試階段,如果依賴在測試階段不生效宅此,那么如果用戶在測試代碼中使用了該依賴中的類机错,會引起測試代碼編譯失敗。
最后是打包階段父腕,如果依賴在打包階段不生效弱匪,簡單來說,打包后的文件中不會存在該依賴的jar包璧亮。這樣打包后的文件萧诫,在后續(xù)運行階段需要另外提供該依賴的jar包斥难,否則當jvm嘗試將該依賴中的class文件加載到內(nèi)存中時,會報錯找不到類帘饶。在不同的打包場景下哑诊,依賴是否生效的具體表現(xiàn)也是不同的,需要具體場景具體分析:
- 打war包:依賴是否生效意味著及刻,該依賴的jar包是否出現(xiàn)在WEB-INF/lib目錄中
- maven-assembly-plugin打jar包(包含依賴的包):依賴是否生效意味著镀裤,是否將該依賴的jar包打進去(默認配置下的表現(xiàn),實際上assembly-plugin配置自由度很高)
- maven-jar-plugin打普通的jar包(只包含主代碼):依賴是否生效意味著缴饭,如果指定了addClasspath暑劝,那么MANIFEST.MF中是否包含該jar包的路徑
依賴在上述三個階段是否生效有多種排列組合,maven定義了多個scope來描述這些場景茴扁,官方文檔說明中共有6種scope:compile provided runtime test system import铃岔,常用和需要關注的有5種 compile provided system runtime test,而import比較特殊峭火,使用的場景和其他幾種完全不同毁习,這里暫時不討論。
這些scope和依賴在三個階段是否生效的關系如下:
編譯 | 測試 | 打包 | |
---|---|---|---|
compile | 是 | 是 | 是 |
runtime | 否 | 是 | 是 |
provided/system | 是 | 是 | 否 |
test | 否 | 是 | 否 |
可以看到卖丸,依賴配置任何scope對于測試階段都是生效的纺且,相當于scope對測試其實是透明的,只要聲明了依賴稍浆,對測試來說就是生效的载碌。
那么這幾種scope一般用在什么場景下呢?
compile是默認的配置衅枫,適用的場景是:如果依賴在編譯階段需要引入(用戶寫的主代碼引用了依賴中的類)嫁艇,而且期望打包到lib中,那么就要聲明為compile弦撩。
runtime適用的場景:編譯階段不需要步咪,比如對于jdbc的具體實現(xiàn)mysql-jdbc,由于代碼中直接使用的是jdbc的接口益楼,所以編譯階段不需要依賴mysql-jdbc猾漫,如果同時我們希望將mysql-jdbc打包到lib中,以供運行階段使用感凤,則需要將該依賴聲明為runtime悯周。
provided適用的場景:依賴在編譯階段需要,但是我們不希望將該依賴打包到lib中陪竿,因為執(zhí)行的容器已經(jīng)提供了該jar包禽翼,如果我們重復提供可能會引起jar包沖突,比如打包flink任務的jar包時,flink相關的依賴闰挡,引擎已經(jīng)提供了仇矾,就不需要也不應該打包進去。
system可以認為和provided一致解总,只是必需要額外指定一個本地路徑,這個一般不推薦使用姐仅。
test適用的場景很單一花枫,如果一個依賴只是和測試代碼相關的,主代碼不使用掏膏,那就就聲明為test劳翰,該依賴在編譯和打包時都會忽略。
Maven中的依賴傳遞
首先要明確的是依賴傳遞解決的是什么問題馒疹,場景如下:
項目A依賴項目B佳簸,項目B依賴項目C,寫作A->B颖变,B->C
那么項目A對項目C的依賴關系是怎樣的生均?這就是依賴傳遞要解決的問題。
依賴傳遞的復雜性主要體現(xiàn)在不同scope下的表現(xiàn)不同腥刹,如圖所示:
https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html
上表中左側的列是外層依賴(A->B)的scope马胧,第一行是內(nèi)層依賴(B->C)的scope,那么A->C的依賴結果為交叉點的結果衔峰。
結果說明:“-”表示不依賴佩脊,不依賴的結果是:A項目在編譯、測試垫卤、打包時都不能訪問C項目威彰。
上表有幾個典型的規(guī)律:
1.provided/test的依賴一定不會傳遞到外層
2.compile的依賴傳遞到外層時和外層的依賴scope相同
3.runtime的依賴傳遞到外層時和外層的依賴scope相同,除非外層是compile穴肘,會保持runtime
上述的規(guī)律還不足以明確地指導我們的使用歇盼,我們從具體的場景進行分析,分為兩類問題:
- 問題一:如果我是A的開發(fā)梢褐,當我聲明A->B的scope時旺遮,對A->C的依賴有什么影響?
- 問題二:如果我是B的開發(fā)盈咳,當我聲明B->C的scope時耿眉,對A->C的依賴有什么影響?
問題一:
最常見的場景鱼响,我聲明了A->B為compile鸣剪,這已經(jīng)是對B最強的依賴了,對于B內(nèi)部的依賴C
如果是compile,會將compile傳遞出來筐骇,C在我的編譯和打包階段都會生效
-
如果是runtime债鸡,那我對C的依賴也是runtime,編譯階段我不能使用C了铛纬,畢竟B也不能用厌均,我對B的內(nèi)部的C權限也不能更高,這一點符合直覺告唆;在打包時棺弊,C會打包到lib包中,這個需求是顯然的擒悬,否則很可能B項目運行階段會報錯模她。
那么我可以將C聲明為provided嗎?可以懂牧,只要我確信這個包在運行時會提供侈净,這樣帶來的好處是可以避免jar包沖突,這樣引入了一個技巧僧凤,為了避免C的jar包沖突畜侦,除了到每一個B依賴中排除掉依賴之外,也可以在A項目中躯保,顯示聲明C為provided夏伊,這樣更加簡潔高效一些
如果是provided或者test,我應當清楚吻氧,我不會對C有依賴
第二個場景溺忧,我聲明了A->B為provided,B不會打入我的jar包盯孙,而運行時會提供B的jar包鲁森,對于B內(nèi)部的依賴C
- 如果是compile,此時我對C的依賴為provided振惰,即我可以編譯階段使用C歌溉,但C也不會打入我的jar包。
- 如果是runtime骑晶,我對C的依賴變成了provided痛垛,在編譯階段看來,這似乎是一種強化桶蛔,B不能直接使用C的類匙头,我卻可以了;在打包階段看來仔雷,被弱化了蹂析,我不會將C打入自己的jar包舔示,這一點與外層B的provided是一致的
- 如果是provided或者test,我應當清楚电抚,我不會對C有依賴
第三個場景惕稻,我聲明了A->B為runtime,B會打入我的jar包蝙叛,對于B內(nèi)部的依賴C俺祠,編譯階段我一定不能使用,畢竟B我都不能使用借帘,這一點是符合直覺的锻煌;那么打包階段呢,我會將compile和runtime的C打入jar包姻蚓,忽略provided和test
第四個場景,我聲明了A->B為test匣沼,同樣B的provide和test不會被我訪問到狰挡,但是B的compile和runtime依賴我可以在測試中自由使用,這里runtime似乎也升級了
問題二:
最常見的場景释涛,我聲明了B->C為compile加叁,因為我對C的依賴是足夠強的,我會期望外部對我的依賴scope也會透傳到對C的依賴唇撬。
第二個場景它匕,我聲明了B->C為provided或test,這意味著外界對C完全不可見窖认,不能編譯豫柬,也不會打包自己的項目。
第三個場景扑浸,我聲明了B->C為runtime烧给,外部對我的依賴scope也會透傳到對C的依賴,這個其實是不太安全的喝噪,這個時候我會發(fā)現(xiàn)我編譯階段不能依賴的C础嫡,外部反而可以了,這個時候我對投傳下來的compile降級為runtime酝惧,其他的未做處理榴鼎。