Maven 的依賴
在上一節(jié)中,我們重點(diǎn)介紹了 Maven 的項(xiàng)目對(duì)象模型(POM),本節(jié)我們重點(diǎn)介紹另一個(gè)重要概念–依賴。我們會(huì)介紹什么是依賴,以及在我們平時(shí)的工作中的最佳實(shí)踐。
1. 何為依賴?
依賴即為本項(xiàng)目對(duì)其他項(xiàng)目的引用,這里的其他項(xiàng)目可以是外部項(xiàng)目,也可以是內(nèi)部項(xiàng)目。我們?cè)陂_發(fā)項(xiàng)目的過程中,將其他項(xiàng)目作為依賴引用進(jìn)來,最終在打包的過程中,依賴會(huì)和我們開發(fā)的項(xiàng)目打包到一起來運(yùn)行。
在我們的項(xiàng)目沒有使用 Maven 的時(shí)候,我們需要手動(dòng)去管理我們的依賴,例如:添加依賴,刪除依賴。在使用了 Maven 之后,我們可以在 pom.xml 文件里面看到我們所有的依賴,并且可以靈活的管理項(xiàng)目的依賴。
在上圖中,我們可以清晰的看到我們項(xiàng)目目前所引用的依賴。
1.1 依賴的范圍
Maven 在編譯和運(yùn)行以及執(zhí)行測(cè)試用例的時(shí)候,分別會(huì)使用不同的 classpath。而 Maven 的依賴范圍則是用來控制依賴與不同 classpath 關(guān)系的。
Maven 的依賴范圍分為以下幾種:
- compile: 編譯依賴范圍。Maven 默認(rèn)的依賴范圍,該范圍的依賴對(duì)編譯,運(yùn)行,測(cè)試不同的classpath 都有效。例如我們項(xiàng)目中的 spring-boot-starter;
- test: 測(cè)試依賴范圍。該依賴范圍只對(duì)測(cè)試 classpath 有效,在編譯項(xiàng)目或者運(yùn)行項(xiàng)目的時(shí)候,是無法使用此類依賴的。例如我們項(xiàng)目中的 spring-boot-starter-test;
- provided: 已提供依賴范圍。該 Maven 依賴對(duì)于編譯和測(cè)試的 classpath 有效,但是在運(yùn)行時(shí)無效;
- runtime: 運(yùn)行時(shí)依賴范圍。顧名思義,該依賴范圍對(duì)測(cè)試和運(yùn)行的 classpath 有效,但是在編譯時(shí)無效;
- system: 系統(tǒng)依賴范圍。該依賴范圍與 classpath 的關(guān)系與 provided 依賴范圍是相同的。但是,在使用時(shí)需要謹(jǐn)慎注意,因?yàn)榇祟愐蕾嚧蠖鄶?shù)是與本機(jī)綁定的,而不是通過Maven倉庫解析出來的,切換環(huán)境后,可能會(huì)導(dǎo)致依賴失效或者依賴錯(cuò)誤。
2. 傳遞性依賴
目前我們的項(xiàng)目只引用了兩個(gè)依賴,spring-boot-starter 和 spring-boot-starter-test,但是是這樣子的嗎?
為了能夠更清晰的看到我們項(xiàng)目依賴的結(jié)構(gòu),我們可以在 IDEA 里面安裝 Maven Helper 插件。
安裝好之后,我們打開項(xiàng)目的 pom.xml 文件,可以看到在文件的下方多了一個(gè) Dependency Analyzer按鈕。打開之后,如下圖。
從這里我們就可以看到,其實(shí)我們不只是引入了兩個(gè)包,而是引入了很多個(gè)包,這是為什么呢?
答案是因?yàn)?Maven 的傳遞性依賴機(jī)制。
在我們這個(gè)項(xiàng)目中,我們引入了 spring-boot-starter 依賴,并且該依賴的范圍是 compile,但是 spring-boot-starter 作為一個(gè)項(xiàng)目也有自己的依賴,在這其中的依賴范圍為 compile 的依賴,則會(huì)自動(dòng)轉(zhuǎn)換成我們項(xiàng)目的依賴,例如 spring-boot 依賴,logback-core 依賴。
所以,有了 Maven 的傳遞性依賴機(jī)制之后,我們?cè)谑褂靡粋€(gè)依賴的時(shí)候,就不再需要考慮它又依賴了哪些,而是直接使用即可,其他的事情 Maven 會(huì)自動(dòng)幫我們做完。
3. 最短路徑原則
有了傳遞性依賴能夠大大節(jié)省我們?cè)诠芾硪蕾嚂r(shí)候所耗費(fèi)的精力。但是,如果傳遞性依賴出了問題我們應(yīng)該如何解決呢?首先,我們應(yīng)該知道的是傳遞性依賴是從哪條依賴路徑引用進(jìn)來的。
在我們的項(xiàng)目中就存在這樣的例子。我們可以看到如下兩條不同的引用路徑:
1. spring-boot-starter --> spring-boot --> spring-aop --> spring-core
2. spring-boot-starter --> spring-core
這個(gè)時(shí)候,我們可以看到,兩條路徑最終引用的 spring-core 版本都是 5.2.5-RELEASE。但是如果引用的 spring-core 版本不同,Maven 會(huì)怎么做呢?
使用最短路徑原則,路徑2中的 spring-core 版本會(huì)本引用,這樣就不會(huì)造成重復(fù)依賴的問題產(chǎn)生。
4. 最佳實(shí)踐
4.1 排除依賴
傳遞性依賴可以幫助我們簡化項(xiàng)目依賴的管理,但是同時(shí)也會(huì)帶來其他的不必要的風(fēng)險(xiǎn),例如:會(huì)隱式地引入一些依賴,這些依賴可能并不是我們希望引入的,或者這些隱式引入的依賴是 SNAPSHOT 版本的依賴。依賴的不穩(wěn)定導(dǎo)致了我們項(xiàng)目的不穩(wěn)定。
在我們的項(xiàng)目中,spring-boot-starter-test 依賴中排除了 junit-vintage-engine 依賴是由于我們使用的 springboot 版本是 2.2.6-RELEASE,對(duì)應(yīng)的 Junit 版本是 5.x,但 junit-vintage-engine 依賴中包含了 4.x 版本的 Junit,此時(shí)我們就可以將該依賴排除。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
在 exclusions 標(biāo)簽中,可以有多個(gè) exclusion 標(biāo)簽,用來排除不需要的依賴。
4.2 依賴歸類
在我們實(shí)際的開發(fā)過程中,我們可能會(huì)需要整合很多第三方框架,在整合這些框架的時(shí)候,往往需要在 pom.xml 里面添加多個(gè)依賴來完成整合。而這些依賴往往是需要保持相同版本的,在升級(jí)框架的時(shí)候,都是要統(tǒng)一升級(jí)到一個(gè)相同的版本。
如下圖,我們可以看到,在引入 dubbo 框架的時(shí)候,我們需要引入兩個(gè)相關(guān)的依賴,而且版本號(hào)是相同的,這個(gè)時(shí)候,我們就可以把對(duì)應(yīng)的版本號(hào)提取出來,放到 properties 標(biāo)簽里面,作為一個(gè)全局參數(shù)來使用。類似于 Java 語言中抽象的思想。
這時(shí)候,我們可以看到,如果在將來的某一天我們需要升級(jí)升級(jí) dubbo 框架對(duì)應(yīng)的版本,只需要修改 properties 中的版本號(hào),就能將所有依賴的版本一起升級(jí)。
4.3 依賴優(yōu)化
我們?cè)倩剡^頭來看一下 Maven Helper 工具所展示的場(chǎng)景
我們?cè)谶@個(gè)工具中可以看到我們項(xiàng)目現(xiàn)在引入的所有的依賴,可以看到哪些是我們用到的,哪些是沒有用來的,依賴與依賴之間是否存在沖突。如果出現(xiàn)了上述情況,我們就可以通過刪除或者依賴排除的方式來將我們不需要的依賴刪除掉,從而使我們的項(xiàng)目更簡潔。
5. 小結(jié)
本節(jié)中,我們介紹了 Maven 中的一個(gè)重要的概念–依賴,介紹了什么是依賴,以及依賴的幾個(gè)特性,最后我們也總結(jié)了在平時(shí)的工作中常常會(huì)用到的依賴優(yōu)化的方式,能夠幫助我們更好的管理項(xiàng)目的依賴。