3.5.5. Maven依赖管理 (Dependency Management)

在本章的simple样例中,Maven处理了JUnit依赖的坐标——junit:junit:3.8.1,指向本地Maven仓库中的/junit/junit/3.8.1/junit-3.8.1.jar。这种基于Maven坐标的定位构件的能力能让我们在项目的POM中定义依赖。如果你检查simple项目的pom.xml文件,你会看到有一个文件中有一个段专门处理dependencies,那里面包含了一个单独的依赖——JUnit。

一个复杂的项目将会包含很多依赖,也有可能包含依赖于其它构件的依赖。这是Maven最强大的特征之一,它支持了传递性依赖(transitive dependencies)。假如你的项目依赖于一个库,而这个库又依赖于五个或者十个其它的库(就像Spring或者Hibernate那样)。你不必找出所有这些依赖然后把它们写在你的pom.xml里,你只需要加上你直接依赖的那些库,Maven会隐式的把这些库间接依赖的库也加入到你的项目中。Maven也会处理这些依赖中的冲突,同时能让你自定义默认行为,或者排除一些特定的传递性依赖。

让我们看一下你运行前面的样例的时候那些下载到你本地仓库的依赖。看一下这个目录:~/.m2/repository/junit/junit/3.8.1/。如果你一直跟着本章的样例,那么这里会有文件junit-3.8.1.jarjunit-3.8.1.pom,还有Maven用来验证已下载构件准确性的校验和文件。需要注意的是Maven不只是下载JUnit的JAR文件,它同时为这个JUnit依赖下载了一个POM文件。Maven同时下载构件和POM文件的这种行为,对Maven支持传递性依赖来说非常重要。

当你把项目的构件安装到本地仓库时,你会发现在和JAR文件同一目录下,Maven发布了一个稍微修改过的pom.xml的版本。存储POM文件在仓库里提供给其它项目了该项目的信息,其中最重要的就是它有哪些依赖。如果项目B依赖于项目A,那么它也依赖于项目A的依赖。当Maven通过一组Maven坐标来处理依赖构件的时候,它也会获取POM,通依赖的POM来寻找传递性依赖。那些传递性依赖就会被添加到当前项目的依赖列表中。

在Maven中一个依赖不仅仅是一个JAR。它是一个POM文件,这个POM可能也声明了对其它构件的依赖。这些依赖的依赖叫做传递性依赖,Maven仓库不仅仅存贮二进制文件,也存储了这些构建的元数据(metadata),才使传递性依赖成为可能。下图展现了一个传递性依赖的可能场景。

Maven处理传递性依赖

Figure 3.7. Maven处理传递性依赖


在上图中,项目A依赖于项目B和C,项目B依赖于项目D,项目C依赖于项目E,但是项目A所需要做的只是定义对B和C的依赖。当你的项目依赖于其它的项目,而这些项目又有一些小的依赖时(向Hibernate, Apache Struts 或者 Spring Framework),传递性依赖使之变得相当的方便。Maven同时也提供了一种机制,能让你排除一些你不想要的传递性依赖。

Maven也提供了不同的依赖范围(dependency scope)。Simple项目的pom.xml包含了一个依赖——junit:junit:jar:3.8.1——范围是test。当一个依赖的范围是test的时候,说明它在Compiler插件运行compile目标的时候是不可用的。它只有在运行compiler:testCompilesurefire:test目标的时候才会被加入到classpath中。

当为项目创建JAR文件的时候,它的依赖不会被捆绑在生成的构件中,他们只是用来编译。当用Maven来创建WAR或者EAR,你可以配置Maven让它在生成的构件中捆绑依赖,你也可以配置Maven,使用provided范围,让它排除WAR文件中特定的依赖。provided范围告诉Maven一个依赖在编译的时候需要,但是它不应该被捆绑在构建的输出中。当你开发web应用的时候provided范围变得十分有用,你需要通过Servlet API来编译你的代码,但是你不希望Servlet APIJAR文件包含在你web应用的WEB-INF/lib目录中。