第四章:坐标和依赖
正如第1章所述,Maven的一大功能是管理项目依赖。为了能自动化地解析任何一个Java构件,Maven就必须将它们唯一标识,这就是依赖管理的底层基础——坐标。
4.1 何为Maven坐标
关于坐标(Coordinate),大家最熟悉的定义应该来自于立体几何。在一个立体坐标系中,该立体空间内的任何一个点,都能够用坐标(x,y,z)唯一标识。在实际生活中,我们可以将地址看成一种坐标。省市县等一系列信息同样可以唯一标识城市中的任一居住地址,邮局和快递公司正是基于这样一种坐标进行邮件寄送的。
对应于几何中的点和城市中的地址,Maven世界中拥有数量非常巨大的构件(如jar),在Maven为这些构件引入坐标概念之前,我们无法使用任何一种方式来唯一标识所有这些构件。因此,当需要用到Spring Framework依赖的时候,大家会去Spring Framework网站寻找,当需要用到log4j依赖的时候,大家又会去Apache网站寻找。又因为各个项目的网站风格迥异,大量的时间花费在了搜索、浏览网页和下载构件。Maven定义了这样的一组规则:世界上任何一个构件都可以使用Maven坐标唯一标识,Maven坐标的元素包括GroupId、artfactId、version、packaging、classifier,现在只要我们提供正确的坐标,Maven就能够帮助我们找到对应的构件。
也许你会奇怪,“Maven是从哪里下载构件呢?”答案很简单,Maven内置了一个中央仓库地址(http://repo1.maven.org/maven2),该中央仓库包含了世界上大部分流行的开源项目构件,Maven会在需要的时候去那里下载。
在我们开发Maven项目的时候,也需要定义适当的坐标,这是Maven强制要求的。在这个基础上,其它的Maven项目才能引用该项目生成的构件。
4.2 坐标详解
Maven坐标为各种构件引入了秩序,任何一个构件都必须明确定义自己的坐标,而一组Maven坐标是通过一些元素定义的,它们是GroupId、artfactId、version、packaging、classifier。坐标定义如下:
<groupId>org.sonatype.nexus</groupId> <artifactId>nexus-indexer</artifactId> <version>2.0.0</version> <packaging>jar</packaging>
- groupId:定义当前Maven项目所属的实际项目。首先,Maven项目和实际项目不一定是一对一的关系。比如SpringFramework这一实际项目,其对应的Maven项目会有很多,如spring-core、spring-context等。这是由于Maven中模块的概念,因此一个实际项目往往会被划分为很多模块。推荐的做法是使用项目隶属的组织或公司的反向域名作为前缀,后跟实际项目名称。上例中,GroupId为org.sonatype.nexus,org.sonatype表示Sonatype公司建立的一个非营利性组织,nexus表示Nexus这一实际项目。
- artifactId:定义实际项目中的一个Maven项目模块,推荐的做法是使用实际项目名称作为前缀,后跟模块名称。上例中,artfactId是nexus-indexer,使用了实际项目名nexus作为前缀,这样做的好处是方便寻找实际构件。默认情况下,Maven生成的构件,其文件名会以artfactId作为开头。
- version:定义Maven项目当前所处的版本,如上例中nexus-indexer的版本是2.0.0。
- packaging:定义项目的打包方式。首先,打包方式通常于所生成构件的文件扩展名对应,如上例中packaging为jar,最终的文件名为nexus-indexer-2.0.0.jar。其次,打包方式会影响到构件的生命周期。
- classifier:该元素用来帮助定义构建输出的一些附属构件。附属构件与主构件对应,如上例中的主构件是nexus-indexer-2.0.0.jar,该项目可能还会用过使用一些插件生成nexus-indexer-2.0.0-javadoc.jar、nexus-indexer-2.0.0-sources.jar这样一些附属构件,其中包含了Java文档和源代码。这时候,javadoc和sources就是这两个附属构件的classifier。这样附属构件也就拥有了自己唯一的坐标。
上述的5个元素中,groupId、artfactId、version是必须定义的,packaging是可选的(默认的打包方式的jar),而classifier是不能直接定义的。
4.3 依赖的配置
一个依赖的声明可以包含如下的一些元素:
<project> ...... <dependencies> <dependency> <groupId>org.springframework</groupId> //项目名称(依赖的基本坐标) <artifactId>spring-core</artifactId> //模块名称(依赖的基本坐标) <version>2.5.6</version> //所处版本(依赖的基本坐标) <type>...</type> //打包类型(默认jar) <scope>...</scope> //依赖范围(默认compile) <optional>...</optional> //依赖是否可选 <exclusions> //排除传递性依赖 <exclusion>...</exclusion> </exclusions> </dependency> </dependencies> ...... </project>
4.4 依赖范围
Maven在编译项目主代码的时候需要使用一套classpath,Maven在编译和执行测试的时候会使用一套classpath,Maven在实际运行项目的时候会使用一套classpath。依赖范围就是用来控制依赖与classpath(编译classpath、测试classpath、运行classpath)的关系:
- compile:编译依赖范围(默认)。使用此依赖范围的Maven依赖,对于编译、测试、运行三种classpath都有效。
- test:测试依赖范围。使用此依赖范围的Maven依赖,只对测试classpath有效,在编译主代码或者运行项目的时候将无法使用此类依赖。典型的例子是JUnit,它只有在编译测试代码及运行测试的时候才需要。
- provided:已提供依赖范围。使用此依赖范围的Maven依赖,对于编译和测试classpath有效,但是在运行时无效。典型的例子是servlet-api,编译和测试项目的时候需要该依赖,但是在运行项目的时候,由于容器已经提供,就不需要Maven重复引入。
- runtime:运行时依赖范围。使用此依赖范围的Maven依赖,对于测试和运行classpath有效,但在编译主代码时无效。典型的例子是JDBC驱动实现,项目主代码的编译只需要JDK提供JDBC接口,只有在执行测试和运行项目的时候才需要实现上述接口的具体JDBC驱动。
- system:系统依赖范围。使用此依赖范围的Maven依赖,和provided依赖范围一致。但是使用system范围时必须通过systemPath元素显式地指定依赖文件的路径。由于此类依赖不是通过Maven仓库解析的,而且往往于本机系统绑定,可能造成构件的不可移植,因此应该慎重使用。
- import(Maven 2.0.9及以上):导入依赖范围。该依赖范围不会对三种classpath产生实际的影响【后续完善】。
4.5 传递性依赖
4.5.1 何为传递性依赖