Maven的亮点之一就是依赖管理,其介绍依赖管理的产品文档竟然长达15页之多,可能这个概念的复杂度不是很小,值得关注,值得学习。
以下是一个POM中典型的一段关于依赖的片段:
<!--Code highlighting produced by Actipro CodeHighlighter (freeware) http://www.CodeHighlighter.com/--> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> ... <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.0</version> <type>jar</type> <scope>test</scope> <optional>true</optional> </dependency> ... </dependencies> ... </project>
你的项目依赖于A,A又依赖于B。你的项目是否要声明你依赖于B? Maven的回答是它帮你自动管理这种依赖的传递,你不需要声明你依赖于B,由Maven来做。
不难就会想到一种冲突的情况,如A -> B -> C -> D 2.0,同时存在A -> E -> D 1.0,那么应该使用D1.0还是D2.0呢?用Maven的话来说,这件事情叫做“Dependency Mediation”(依赖仲裁),当出现这种冲突时,采用“Nearest Definition”的解决办法,即采用最短路径,在上述例子中仲裁的结果是D1.0,因为它的路径更短。如果两个路径长短是一样的呢?那么只能是谁先出现就选谁了。上述例子中如果你显式声明A依赖于D2.0,那么Maven就会帮你选取D2.0而不是D1.0。
2、Dependency Scope (依赖范围)
举例来说,你开发时需要做测试,你需要依赖于junit的jar,但是部署应用时并不需要它,因为单元测试不会在生产环境上跑,也就是说最终打包的jar或者war不包含junit的jar。又如你开发web程序,你的servlet/jsp进行编译需要依赖于servlet-jsp的标准api(J2EE的jar),但是部署时也是不需要它的,因为你的应用服务器肯定有这些东西。
因此,Maven考虑了6中可能的scope供选择:
- compile: 默认的scope。编译、测试、打包全都需要。compile参与依赖传递,就是说,你的项目A依赖于B(依赖scope是compile),项目C依赖于你的项目A,那么C也就依赖于B。
- provided: 表示JDK或者容器会在Runtime时提供这些(jar),如上面说到的servlet api。provided的东西在编译和测试时会用到,不参与传递依赖。
- runtime: 表示编译时不需要,但测试和运行时需要,最终打包时会包含进去。
- test: 只用于测试阶段(测试的编译和测试的运行),典型的就是junit的jar。
- system: 和provided类似,但要求jar是你的系统里已有的,不会在repository里找,如rt.jar,tools.jar这些。
- import: 简单的说,你的项目的pom可以继承另一个项目的pom,从而继承了父项目的依赖关系,但是因为之后single inheritance的限制,所以创造了import,使得你可以“导入”或者说“继承”任何一到多个项目的依赖关系。
如果你要考虑更细一些,你的项目依赖于A(scope1),A依赖于B(scope2),那么你的项目对B的依赖应该算是哪个scope呢?官方文档上有个表格,列举了scope1和scope2的一个矩阵关系(各自都可能是compile/provided/runtime/test),然后最后结果应该是什么。
3、Dependency Classier
用于再次区分不同类别的东西,如在jdk1.4下编译的东西还是jdk1.5下出来的云云。