Maven工程通過pom.xml里的<dependency>來定義依賴項。當然,我們不會少定義依賴項,否則編譯不通過。不過,如果我們多定義了依賴項雖然不會造成災難,但可能會造成一些問題,比如:
- 多余的依賴項造成閱讀和理解的困難。
- Spring的@ComponentScan將掃描出多余的組件。特別地,如果這些組件還需要配置才能使用則造成一些意想不到的問題,並且發現和糾正這些問題也比較困難。
- 如果多余的依賴項為compile或runtime作用域,則其它依賴本工程的工程也將依賴這個多余的工程。如果運行時出了問題則更難處理。
因此,我們希望在pom.xml里定義的依賴項不多不少,並且其作用域(<scope>)也恰到好處,剛好滿足本工程的需要。問題是該如何進行檢查呢?答案是使用maven-dependency-plug插件。
maven-dependency-plugin插件簡介
maven-dependency-plugin用於管理依賴項,提供了多個可執行的goal,在此我們只關心其中的analyze和tree兩個。
下面是dependency:analyze的一個輸出樣例片斷:
>mvn dependency:analyze
[INFO] --- maven-dependency-plugin:2.8:analyze (default-cli) @ wtp-core --- [WARNING] Used undeclared dependencies found: [WARNING] org.springframework:spring-beans:jar:3.2.3.RELEASE:compile [WARNING] Unused declared dependencies found: [WARNING] junit:junit:jar:4.7:test [WARNING] org.springframework:spring-test:jar:3.2.3.RELEASE:test [WARNING] org.slf4j:jcl-over-slf4j:jar:1.6.1:runtime [WARNING] org.slf4j:slf4j-log4j12:jar:1.6.1:runtime [WARNING] commons-lang:commons-lang:jar:2.5:test
官方文檔里說,dependency:analyze是通過分析bytecode來輸出報告。上面的輸出有兩部分,一是Used undeclared dependencies(使用但未定義的依賴項),二是Unused declared dependencies(未使用但卻定義的依賴項)。
- Used undeclared dependencies
該列表列出的是程序代碼直接用到的、但並沒在pom.xml里定義的依賴項。 - Unused declared dependencies
該列表列出的是程序代碼沒用到的、但在pom.xml里定義的依賴項。注意,該列表可能不准確,因為程序代碼可能使用了反射技術,在運行時需要這些jar包存在。
再說說dependency:tree,下面是一個輸出樣例片斷:
>mvn dependency:tree
com.wisetop.wtp:wtp-core:jar:2.2.1-SNAPSHOT +- junit:junit:jar:4.7:test +- org.springframework:spring-test:jar:3.2.3.RELEASE:test +- org.slf4j:slf4j-api:jar:1.6.1:compile +- org.slf4j:jcl-over-slf4j:jar:1.6.1:runtime +- org.slf4j:slf4j-log4j12:jar:1.6.1:runtime | \- log4j:log4j:jar:1.2.16:runtime +- org.springframework:spring-core:jar:3.2.3.RELEASE:compile | \- commons-logging:commons-logging:jar:1.1.1:provided +- org.springframework:spring-context:jar:3.2.3.RELEASE:compile | +- org.springframework:spring-aop:jar:3.2.3.RELEASE:compile | +- org.springframework:spring-beans:jar:3.2.3.RELEASE:compile | \- org.springframework:spring-expression:jar:3.2.3.RELEASE:compile +- org.springframework:spring-tx:jar:3.2.3.RELEASE:compile | \- aopalliance:aopalliance:jar:1.0:compile +- org.springframework:spring-context-support:jar:3.2.3.RELEASE:compile +- commons-beanutils:commons-beanutils:jar:1.8.3:compile +- dom4j:dom4j:jar:1.6.1:compile | \- xml-apis:xml-apis:jar:1.0.b2:compile +- com.wisetop.extra.oracle:wt-oracle-xdb6:jar:11.2.0.3:compile | \- com.wisetop.extra.oracle:wt-oracle-ojdbc6:jar:11.2.0.3:compile +- commons-lang:commons-lang:jar:2.5:test +- org.quartz-scheduler:quartz:jar:1.7.3:compile +- commons-codec:commons-codec:jar:1.4:compile \- javax.servlet:javax.servlet-api:jar:3.0.1:provided
該命令列出了各依賴項、以及傳遞出來的依賴項。通過此命令我們可以識別出依賴項來自何處,特別地,如果你只關心某個特定的依賴項,比如上面的log4j是怎么出來的,你可加上過濾條件:
>mvn dependency:tree -Dincludes=log4j:log4j [INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ wtp-core --- [INFO] com.wisetop.wtp:wtp-core:jar:2.2.1-SNAPSHOT [INFO] \- org.slf4j:slf4j-log4j12:jar:1.6.1:runtime [INFO] \- log4j:log4j:jar:1.2.16:runtime
當然,你可以簡寫為:
>mvn dependency:tree -Dincludes=*:log4j
其輸出是一樣的。
另外,一個依賴項可能來自多處,你可加上"-Dverbose"參數進行查看。例如,下面的命令將顯示spring-core可來自多處:
>mvn dependency:tree -Dincludes=*:spring-core -Dverbose [INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ wtp-core --- [INFO] com.wisetop.wtp:wtp-core:jar:2.2.1-SNAPSHOT [INFO] +- org.springframework:spring-test:jar:3.2.3.RELEASE:test [INFO] | \- (org.springframework:spring-core:jar:3.2.3.RELEASE:test - omitted for duplicate) [INFO] +- org.springframework:spring-core:jar:3.2.3.RELEASE:compile [INFO] +- org.springframework:spring-context:jar:3.2.3.RELEASE:compile [INFO] | +- org.springframework:spring-aop:jar:3.2.3.RELEASE:compile [INFO] | | \- (org.springframework:spring-core:jar:3.2.3.RELEASE:compile - omitted for duplicate) [INFO] | +- org.springframework:spring-beans:jar:3.2.3.RELEASE:compile [INFO] | | \- (org.springframework:spring-core:jar:3.2.3.RELEASE:compile - omitted for duplicate) [INFO] | +- (org.springframework:spring-core:jar:3.2.3.RELEASE:compile - omitted for duplicate) [INFO] | \- org.springframework:spring-expression:jar:3.2.3.RELEASE:compile [INFO] | \- (org.springframework:spring-core:jar:3.2.3.RELEASE:compile - omitted for duplicate) [INFO] +- org.springframework:spring-tx:jar:3.2.3.RELEASE:compile [INFO] | \- (org.springframework:spring-core:jar:3.2.3.RELEASE:compile - omitted for duplicate) [INFO] \- org.springframework:spring-context-support:jar:3.2.3.RELEASE:compile [INFO] \- (org.springframework:spring-core:jar:3.2.3.RELEASE:compile - omitted for duplicate)
該輸出顯示,我們在pom.xml里直接依賴了spring-core,並且告訴我們如果我們不直接依賴spring-core的話則它也可能來自何處。
關於更多使用方法請參見dependency:tree的說明文檔,以及過濾依賴樹。
使用maven-dependency-plugin對pom.xml文件進行優化
上面說到,優化的目標是不多不少地定義依賴項。不僅依賴項的個數要不多不少,而且依賴項的作用域也要恰到好處。其驗證方法是:
- 檢查是否少定義了依賴項,即執行mvn clean dependency:analyze后,應
- 沒有Used undeclared dependencies列表。
- Unused declared dependencies列表中的依賴項需人工判斷是否有必要存在,若無必要則請刪除。
特別地,對於其中的compile和test依賴項,如果不需要或能被其它依賴項傳遞帶出則請刪除。檢查方法是:先刪除該依賴項重試。如果重試成功則保持刪除,如果不成功則改為test再重試,如果重試不成功則改為compile。如果你熟悉dependency:tree命令,你也可以從該命令的輸出直接看到預期的結果。
既然我們的代碼沒用到那個依賴項,為什么刪除該依賴項可能導致編譯不通過呢?這可能的原因是我們所依賴的依賴項其本身又依賴了其它依賴項,但它們之間是provided或optional關系,導致不傳遞依賴。 - 檢查是否多定義了依賴項,並檢查作用域是否定大了,即執行
mvn clean dependency:analyze -Dmaven.test.skip=true后,應 - 沒有Used undeclared dependencies列表。上面第一步通過后,這里肯定不會出現該列表。
- Unused declared dependencies列表中可以含有任意作用域的依賴項。我們需要去檢查能否縮小作用域的范圍,特別地,需要重點檢查compile作用域是否能縮小到test、provided、runtime和system,如果能則改之。
- 以上步驟中如果修改了pom.xml文件,則請回到第一步重新開始驗證,以保證優化前能編譯通過。
- 執行mvn clean test命令做進一步驗證。
需要特別說明的是:
- 上面第一步是檢查main/java和test/java中所有的代碼,並保證能編譯通過、並且沒缺少依賴項(即沒有Used undeclared dependencies列表),但可能多定了依賴項。
- 第二步比第一步多了“-Dmaven.test.skip=true”參數,使得只生成target/classes、不生成target/test-classes,其含義是僅檢查main/java中的代碼,其意圖是,我們希望找到那些僅被main/java或test/java用到的依賴項,以此盡量縮小作用域、使得作用域定義得恰到好處。
對於某個依賴項, - 如果被main/java用到,則不論是否被test/java用到,其作用域都以main/java為准。
- 如果被test/java用到,並且被main/java間接用到,則作用域也以main/java為准。
請考慮這樣一個場景,假設有A、B兩個依賴項,並且A依賴於B。如果main/java只用到A、沒用到B,而test/java用到B,則需把B定義為與A一樣的作用域。需要說明的是,- 如果test/java也沒用到B,則根本就不需要定義B。Maven的傳遞依賴機制將會自動帶出B。
- 即使main/java沒直接用到B,但編譯時可能需要B存在。例如,A中的某個對象繼承了B中的對象,並且我們當前又再次繼承了A中的這個對象。
- 如果僅被test/java用到,則作用域可定義為test。
- 上面說的“以main/java為准”的含義是保持與Maven的依賴機制相同。
- 上面命令都帶上了clean,除非你清楚命令的含義否則不要去掉。特別地,maven-dependency-plugin所分析的不是源程序,而是編譯后的bytecode。
- 你可通過執行dependency:tree來查看依賴項出自何處,以及你在pom里定義的依賴項本身又傳遞出來了哪些其它依賴項。
- 最后,如果你的系統是多模塊工程,則最好從底向上逐個優化pom,並且,每當你完成優化了一個工程后不要忘記了執行mvn install,這是因為在你繼續到下一個工程時,dependency:analyze只從Maven庫中去找其所依賴的pom.xml文件。