坐标和依赖
坐标详解
Maven坐标为各种构件(artifact)引入了秩序,每一个artifact都必须明确定义自己的坐标,而一组Maven坐标是由以下元素定义的:
<groupId>com.ssozh.mirana</groupId>
<artifactId>mirana-parent</artifactId>
<packaging>pom</packaging>
<version>1.0.0-SNAPSHOT</version>
- groupId:定义当前maven项目隶属于的实际项目。
- maven的项目和实际项目不一定是一对一关系,比如SpringFramework这一实际项目,对应的maven项目会有很多包括spring-core、spring-context等等。
- groupId不应该对应项目隶属的组织和公司,因为一个组织下面会有很多实际项目,如果groupId只定义到组织级别。那么artifactId会非常难以定义
- groupId的表示方式与java包名的表示方式类似,通过与域名反向一一对应。上面的
com.ssozh
是我,mirana
是我定义的实际项目。该groupId应该遇mirana.ssozh.com
对应。
- artifactId:该元素定义实际项目中的一个maven项目。
- 推荐的做法是使用实际项目名称作为artifactId的前缀,比如上面的离职中artifactId是
mirana-parent
。这样做的好处是方便寻找实际构建。 - 默认情况下,maven生成的构建,其文件名会议artifactId作为开头,如
mirana-parent-1.0.0-SNAPSHOT.jar
。这样就可以方便从一个lib文件夹中找到某个项目的一组构建。考虑有4个项目,每个项目都有core模块,如果没有前缀就会看到很多core-1.2.jar
这样的文件。
- 推荐的做法是使用实际项目名称作为artifactId的前缀,比如上面的离职中artifactId是
- version:该元素定义maven项目当前所处的版本,如上例中的版本是快照版本。=>13章会详解。
- packaging:定义maven项目的打包方式。默认使用jar
- classifier:该元素用来帮助定义构建输出的一些附属构件。
- 附属构件与主构件对应,比如javadoc和sources就是一些附属构件,其包含了java文档和源代码。
- 注意:不能直接定义项目的classifier,因为附属构件倍速项目直接默认生成的,而是有附加的插件帮助完成的。
最后,项目构建的文件名是与坐标相对应的,规则为arifactId-version[-classifier].packaging
。
项目(略)
【因为】本项目不打算直接完成书中的项目,因此这个地方也是先看一遍,等自己的东西做好了,再过来补充这个部分。计划包括:
- OSS:可能不应该这么分,因为OSS无非是一种第三方调用而已。
- common:log4j
- admin:包含controller层的项目。
项目pom
项目的主代码
项目的测试代码
构建项目
使用mvn clean install
构建项目,Maven会根据POM配置自动下载锁所需要的依赖构件,执行编译、测试、打包等工作。最后将项目生成的构件 安装到本地仓库中。这时,该模块就能供其他Maven项目使用了。
依赖的配置
其实一个依赖声明可以包含如下一些元素:
<project>
...
<dependencies>
<dependency>
<groupId>...</groupId>
<artifactId>...</artifactId>
<version>...</version>
<type>...</type>
<scope>...</scope>
<optional>...</optional>
<exclusions>
<exclusion>
...
</exclusion>
</exclusions>
</dependency>
....
</dependencies>
....
</project>
根元素project
下的dependencies
可以包含一个或多个dependency
元素,以声明一个或者多个项目依赖。每个依赖可以包含的元素有:
- GAV:坐标。
- type:依赖的类型,对应于项目坐标定义的packaging。大部分情况下该元素省略,默认jar
- scope:坐标的范围,见下文。
- optional:标记依赖是否可选,见下文
- exclusions:用来排除传递性依赖,见下文。
依赖范围(可感知问题)
JUnit以依赖的范围是test,测试范围用元素scope
表示。本节将详细讲解什么是测试范围,以及各种测试范围的效果和用途。
首先需要知道,Maven在编译项目主代码的时候需要使用一套classpath。比如编译项目主代码的时候需要用到spring-core,该文件以依赖的方式被引入到classpath中。
其次,maven在编译和执行测试的时候会使用另外一套classpath。比如JUnit也会议依赖的方式引入到测试使用的classpath中,不同的是这里的依赖范围是test。
最后,实际运行maven项目的时候,又会使用一套classpath,上例中的spring-core需要在该classpath中,而JUnit则不需要。
依赖范围就是用来控制起来与这三种classpath(编译classpath、测试classpath、运行classpath)的关系。Maven有以下几种依赖范围:
-
compile:【默认】编译依赖范围。使用此依赖范围的maven依赖,对于编译、测试、运行三种classpath都有效。
-
test:测试依赖范围。使用此依赖范围的maven依赖,只对测试classpath有效,在编译主代码或者运行醒目的使用时将无法使用此类依赖。
-
provided:已提供依赖范围。使用此依赖范围的maven依赖,对于编译和测试classpath有效,但在运行时无效。典型的例子是servlet-api,编译和测试项目的时候需要该依赖,但在运行项目的时候,由于容器已经提供,就不想要maven重复地引入一遍。
-
runtime:运行时依赖范围。使用此依赖范围的maven依赖,对于测试和运行classpath有效,但在编译主代码时无效。典型例子是JDBC驱动实现,项目主代码的编译只需要JDK提供的JDBC接口,只有在执行测试或者运行项目的时候才需要实现上述接口的具体JDBC驱动。
-
system:系统以来范围。该依赖于三种classpath的关系,和provided依赖范围完全能一致,但是使用system范围的依赖时必须通过systemPath元素显式地指定依赖文件的路径。由于此类依赖不是通过maven仓库解析的,而且常常与本机系统绑定,可能造成构建的不可移植,另外systemPath元素可以引用环境变量,如:
<dependency> <GAV></GAV> <scope>system</scope> <systemPath>${java.home}/lib/rt.jar</systemPath> </dependency>
-
import:导入依赖范围。该依赖范围不会对三种classpath产生实际的影响,该范围依赖只在
dependencyManagement
元素下才有效果,使用该范围的依赖通常指向一个pom,作用是将目标pom中的dendencyManagement
配置导入并合并到当前POM的dependencyManagement
元素中。- 注意,上述代码中的依赖的type为pom,import范围依赖由于其特殊性,一般都指向打包类型的pom的模块。如果有多个项目,它们使用的依赖版本都是一致的,则就可以定义一个dependencyManagement专门管理依赖的POM,然后在各个项目中导入这些依赖管理配置。
传递性依赖
何为传递性依赖
【问题】:
考虑一个基于spring框架的项目,如果不使用maven则需要手动下载相关依赖,这么做会引入很多不必要的依赖。
另外一个做法就是懒加载,下载一个只有spring框架的jar包,到实际使用的时候,再根据出错信息,或者查询相关文档,加入需要的其他依赖。
【解决方法】:mavn的传递性依赖机制。
有了传递性依赖机制,在使用spring的时候就不用考虑它依赖了什么,也不用担心引入多余的依赖。maven会解析各个直接依赖的POM,将那些必要的间接依赖,以传递性依赖的形式传递到当前的项目中。
依赖性依赖和依赖范围
假设A依赖于B,B依赖于C,我们说A对于B是第一直接依赖,B对C是第二个直接依赖,A对C就是传递性依赖。第一直接依赖和第二直接依赖的范围决定了传递性依赖的范围。依赖范围影响传递性依赖:
- | compile | test | provided | runtime |
---|---|---|---|---|
compile | compile | - | - | runtiom |
test | test | - | - | test |
provided | provided | - | provided | provided |
runtime | runtime | - | - | runtime |
第一数列是第一依赖,第一横列是第二依赖。中间的表格是传递范围。
很明显可以发现,当第二依赖的范围是compile的时候,传递性依赖的范围与第一直接依赖范围一致;当第二依赖范围是test的时候,依赖不会得以传递。
依赖调解
maven引入的传递性依赖机制,让我们不用考虑这些直接依赖引入什么传递性依赖。但有时候,当传递性依赖造成问题的时候,我们就需要清除地知道该传递性依赖是从哪条依赖路径引入的。
Maven依赖调节(dependency mediation)的第一原则是:路径最近者优先。
Maven定义了依赖调解的第二原则:第一声明者优先。在路径长度一样情况下,在POM中依赖声明的顺序最靠前的那个依赖优胜。例如:
A->B->Y(1.0)
A->C->Y(2.0)
如果B的依赖声明C之前,那么Y(1.0)就会被解析使用。
可选依赖
【场景】
假设有这样一个依赖关系,项目A依赖于项目B,项目B依赖于项目X与项目Y,但是B对于X和Y的依赖都是可选依赖。如果都是compile依赖,那么根据传递性,X和Y对于A就是compile范围的依赖。然而由于X、Y是可选依赖,依赖将不会得以传递。
【为什么会有可选依赖】
存在一个项目B实现了两个特性,其中的特性一依赖于X,特性二依赖于Y,而且这两个依赖是互斥的,用户不能同时使用这两个特性。比如B是一个持久层隔离工具包,他支持多种数据库,如Myql和postgreSQL等,在构建这个工具包的时候,需要这两个数据可的驱动程序,但在使用的时候,只会依赖一种数据库。因为出现了可选依赖
【使用方法】
<project>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.10</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>8.4-701.jdbc3</version>
<optional>true</optional>
</dependency>
</dependencies>
</project>
上述XML中,使用<optional>
表示这两个依赖为可选依赖,他们只会对当前项目B产生影响,当其他项目依赖于B的时候,这两个依赖不会被传递。因此当A依赖于B的时候,如果实际使用基于Mysql数据库,那么再项目A中就需要显示地声明mysql-connector-java
这一依赖。
在理想情况下,是不应该使用可选依赖的。在面向对象设计中,有一个单一职责性原则。这个规则在maven项目中也同样使用。
最佳实践
排除依赖
传递性依赖会给项目隐式地加入很多依赖,这极大的简化了项目的管理,但是有些时候这种特性也会带来问题。例如,当前项目有一个第三方依赖,而这个第三方依赖由于某些原因依赖了另外一个类库的SNAPSHOT版本,那么这个SNAPSHOT就会成为当前的传递性依赖,而SNAPSHOT的不稳定性会直接影响到当前的项目。这个时候就需要排除掉该SNAPSHOT,并且在当前项目中声明该类库的某个正式发布的版本。具体用法就是用<exclusions>
标签就行了。
归类依赖
对于同样是来自于spring framework的不同模块,比如spring-core,spring-beans等等。所有这些依赖的版本都是相同的,而且可以预见,如果将来要升级spring framework,这些依赖的版本会一起升级,对于这种情况应该使用<properties>
元素定义maven
属性,定义一个springframework.version
子元素,其值为2.5.6。然后通过${springframework.version}
替换实际值。
优化依赖
【三个命令】
mvn dependency:list
: 显示已解析的依赖和每个依赖的范围。
mvn dependency:tree
:显示依赖树
mvn dependency:analyze
:
- used undeclared dependencies指项目中使用到的,但没有显式声明的依赖。【当这个依赖升级了,可能导致当前项目出错】
- Unused declared dependencies指的是项目中未使用的,但显示声明的依赖【需要注意的是,由于该命令只会分析编译主代码和测试代码需要用到的依赖】
补充
classpath
maven项目分为src目录,resource目录,test/src目录,test/resource目录:
其中src和resource对应到项目的targetclasses目录,如果在src目录调用classpath,则class的根目录为targetclasses
test/src,test/resource对应到test-classes目录,如果在test/src目录调用classpath,则class的根目录为target est-classes
maven的classpath就是java下面的地址,和resources下面的地址。因为最后都会编译/复制到classes中去的。
其实也就是maven中说的主代码所在的位置
这个地方相关的知识点可以查看:
- java.util.Properties
- org.springframework.util.ResourceUtils