Maven 坐标和依赖
Maven 的一大功能就是管理项目依赖,为了能自动化的解析任何一个 Java 构件,Maven 必
须将他们唯一标识,这就是依赖管理的底层基础“坐标”。
1. Maven 坐标是什么
世界上任何一个构件都可以使用 Maven 坐标唯一标识,这些构件其实也就是平时使用的一些 jar、
war 等文件。例如:
<groupId>com.lichee</groupId> <artifactId>lichee-core</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging>
a) groupId
定义当前 Maven 项目隶属的实际项目,groupId 的表示方式与 Java 包名的表示方式类似,
通常与域名反向一一对应。
b) artifactId
该元素定义实际项目中的一个 Maven 项目(模块),推荐的做法是使用实际项目名称作为
artifactId的前缀,好处在于方便寻找实际的构件(构件组)。
c) version
定义 Maven 项目所处的版本,Maven 定义了一套完整的版本规范。
d) packaging
定义 maven 项目的打包方式,默认值是 jar。
e) classifier
定义构建输出一些附属构件,附属构件与主构件对应,例如:javadoc.jar、
sources.jar,这样附属构件也拥有了自己唯一的坐标。
上述 5 个元素,groupId、artifactId、version 是必须要定义的,packaging 是可选的,
classifier 是不能直接定义的。项目构件名的文件名是与坐标相对应的,
一般规则为artifactId-version[-classifier].packaging , [-classifier] 表 示 可 选 。
packing 并非一定与构件扩展名对应,比如 packing 为 maven-plugin 的构件扩展名为 jar。
2. 依赖的配置
跟元素 project 下的 dependencies 可以包含一个或者多个 dependency 元素,
以声明一个或者多个项目依赖。每个依赖可以包含的元素有:
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency>
a) groupId、artifactId和version
依赖的基本坐标,Maven根据坐标才能找到需要的依赖。
b) type
依赖的类型,对应项目坐标定义的packaging,默认值是jar,大部分情况下,
该元素不必声明。
c) scope
依赖范围就是用来控制三种classpath(编译classpath、测试classpath、运行classpath)的关系。
i. compile
编译依赖范围。如果没有指定,就会使用该默认依赖范围。此依赖范围对编译、
测试、运行三种classpath都有效。例如:spring-core
ii. test
测试依赖范围。只对于测试classpath有效。例如:Junit
iii. provided
已提供依赖范围。对于编译和测试classpath都有效,但在运行时无效。
例如:servlet-api
iv. runtime
运行时依赖范围。对于测试和运行classpath有效,但在编译主代码使无效。
例如:JDBC驱动
v. system
系统依赖范围。与classpath的关系和provided依赖范围完全一致。
只是system范围的依赖必须通过systemPath元素显示的指定依赖文件路径。
这类依赖不通过maven仓库解析,往往和本机系统绑定,
可能造成构建的不可移植,应当谨慎使用。
vi. import
导入依赖范围。不会对三种classpath产生实际的影响(暂且不管)。
d) optional
标记依赖是否可选。
在理想的情况下,是不应该使用可选依赖的。使用可选依赖的原因是某一个项目实现可多个特性,
在面向对象设计中,有个单一职责性原则,意指一个类应该只有一项职责,而不是柔和太多的功能。
如:mySQL驱动、postgreSQL驱动。
e) exclusions
用来排除传递性依赖。
传递性依赖会给项目隐式的引入很多依赖,这极大的简化了项目依赖的管理。
但也会带来相应的问题。例如:SNAPSHOT版本的jar不稳定性。
代码中使用exclusions元素声明排出依赖,可以包含一个或者多个exclusion子元素,
所以可以排出一个或者多个传递性依赖。使用此元素只需要groupId和artifactId,
而不需要version。
3. 传递性依赖
如果项目里面的一个依赖,依赖于另外的依赖,那么这个另外的依赖,不需要你手动引入,
也会自动依赖进来,就像传递的感觉一样。Maven 会解析各个直接依赖的 POM,
将那些必要的间接依赖,以传递性依赖的形式引入到当前的项目中。
例如:A -> B B -> C ==> A -> C
a) 当第二依赖的范围是 compile 的时候,传递性依赖的范围与第一直接依赖的范围一致。
b) 当第二直接依赖的范围是 test 的时候,依赖不会得以传递。
c) 当第二依赖的范围是 provided 的时候,只传递第一直接依赖范围也为 provided的依赖,
且传递性依赖的范围同样为 provided。
d) 当第二直接依赖的范围是 runtime 的时候,传递性依赖的范围与第一直接依赖的范围一致,
但 compile 例外,此时传递的依赖范围为 runtime。
4. 依赖调解
传递性依赖机制,一方面大大简化和方便了依赖声明,另一方面,
大部分情况我们只需要关心项目的直接依赖,而不考虑这些直接依赖会引入什么传递性依赖。
不过有的时候,也会造成不小的问题。
a) 调解第一原则:
传递性依赖中,路径最近者优先。
b) 调解第二原则:
第一声明者优先。在依赖路径长度相等的前提下,
在 POM 中依赖声明的顺序决定了谁会被解析使用,顺序最靠前的那个依赖优胜。
5. 最佳实践
a) 排除依赖
2. e)章节已讲。
b) 归类依赖
一类的依赖一起引入,一个项目不同的子模块。例如:spring-aop,spring-core。
c) properties 定义 maven 属性
properties 元素定义的属性,可以使用美元符号和大括弧环绕的方式引入 Maven属性。
例如:${spring.version}
d) 优化依赖
i. dependency:list
显示已解析树。
ii. dependency:tree
显示当前依赖树。
iii. dependency:analyze
分析当前项目的依赖。
1) Use undeclared dependencies
项目使用到,但是没有显示声明的依赖,这意味着隐藏风险,
所以应该显示声明任何项目中直接用到的依赖。
2) Use declared dependencies
项目中没有使用到,但是却显示声明的依赖。这类别的依赖,不能轻易删除声明,
应该仔细分析项目中是否有使用到。比如:analyze 指令只分析编译主代码和测试代码时
用到的依赖,一些执行测试和运行时需要的依赖发现不了。