Maven笔记 二 依赖
先导
再看坐标系统
坐标系统帮助Maven顺利的定位每一个项目,每一个Maven项目都要有一个坐标。
- groupId,组名,指定了当前Maven项目为哪个组织下的哪个实际项目,如
com.gayhub.chatroom
,指定了当前项目是属于gayhub.com
公司的chatroom
项目下的一个子模块。 - artifactId,当前项目的模块名。比如groupId为
com.gayhub.chatroom
,artifactId为chatroom-livestream
,代表当前项目是gayhub.com
公司下的一个聊天室项目(chatroom)下的一个处理实时视频流聊天的子模块(chatroom-livestream)。推荐使用实际项目名称作为前缀,因为Maven打包使用的命名规则是artifactId-version.jar
,如果不加前缀,那么就是诸如livestream-0.0.1.jar
此类的名字,这样汇总到大项目中时,如果大项目的其他子项目中也有livestream
模块,很难分清哪个是哪个。 - version,版本号。
- packaging,打包方式,不必须,默认为jar。
- classifier,生成附属构件,不能直接定义,作用是生成项目的一些附属构件,如文档,源码。
依赖的配置项
一个依赖项目中可以指定一些参数。
- groupId、artifactId、version,不用说了,该依赖在Maven仓库中的坐标,用于找到这个依赖。
- type,该依赖的类型,对应于打包时的
packaging
,默认为jar,不必须声明。 - scope,依赖范围
- optional,是否是可选依赖
- exclusion,用来排除依赖传递性
大部分时候只使用基本的坐标就可以完成工作了,其它的用于应付特殊情况。
scope
Maven有三套classpath,作用于构建的各个阶段,以对依赖进行灵活的控制。
编译主代码的时候,也就是src/main/java
的时候,使用一套classpath,在编译和执行测试的时候,也就是src/test/java
的时候,使用一套classpath,实际运行的时候又有一套classpath。
scope用来控制引入的依赖如何存在于这三个classpath中,可以设置成如下值:
- compile:默认的依赖范围,依赖会存在于编译,测试,运行的classpath中,如
spring-core
。 - test:测试以来范围,依赖会存在于测试的classpath中,如
JUnit
。 - provided:意思依赖在运行时环境中已经提供,所以只在编译和测试的classpath有效,如
servlet-api
。 - runtime:运行时依赖范围,依赖只存在于运行的classpath中,比如
JDBC
,因为我们使用JDBC只需要通过JDBC API,完全不依赖实现类,具体的实现类是要等运行时才会注入的。 - system:系统依赖范围,依赖系统上的文件,它的作用域和
provided
一致,只是它与系统相关,所以如果引用这个依赖可能会对可移植性造成限制。, - import:导入依赖范围,具体不知道干啥的,Maven2.0.9以上才有。
依赖传递性
往往你依赖的一个项目会存在大量的二级依赖,就是你依赖的项目还会依赖其他项目,有两个解决办法,第一个是一个一个的手动去下载那些依赖,再一个就是去下载一般项目都会提供的一个已经带有所有依赖的包,但这时你又要手动去处理这个巨大的依赖包和你项目中本有依赖的冲突,而且有些特性你用不到,那你也得忍受着,它会一股脑地被塞到你的项目中。
得益于坐标系统,依赖传递性会自动的寻找那些二级依赖并作为你自己项目的依赖。
如图,spring-core
是account-email
的一个compile依赖,commons-logging
是spring-core
的一个compile依赖,那么Maven会自动加载commons-logging
,成为account-email
的compile依赖。
下图左边第一列为直接依赖的scope,上面第一行为二级依赖的scope,交叉的格子就是二级依赖会以什么scope被你的项目依赖。
很自然的,比如当直接依赖为test时,二级依赖为compile,这时二级依赖对于你的项目自然应该在test scope中,因为直接依赖都是测试时才会作用的,它的二级依赖凭什么跑到运行时呢?
再比如二级依赖为test时,根本不会传递到你项目的依赖中,这也是很自然的,比如你引入了spring,你对spring开发阶段的单元测试感兴趣吗?那些东西对你没有用。
依赖调解
项目依赖多了难免会有冲突。
Maven如果遇到两个相同的依赖,它的解决办法是最近的最低的优先,比如A->B->C->X
,A->D->X
,那么会以A->D->X
这条路径上的X优先。(A->B代表A引入B)。
如果路径深度相同,Maven2.0.8以及之前选择哪个不确定,在之后的版本中,哪一个先被声明,哪一个优先。
optional
可选依赖应用在什么场景呢?比如一个依赖既支持MySQL,又支持PostgreSQL,那么它需要依赖两个数据库各自的驱动,但是用户只能使用一个数据库,也就是说只有一个功能被使用了,这时候就需要用到可选依赖。
需要注意的是,可选依赖不会被传递到上层,也就是说用户引用了这个支持两个数据库的依赖之后,具体使用哪个需要自行在pom.xml
中配置依赖。
不建议使用可选依赖,因为这不符合单一职能原则,更好的办法是为两种数据库创建单独的依赖
排除依赖
比如你依赖的项目依赖了一个SNAPSHOT版本的依赖,你不允许SNAPSHOT版本的库出现在你的项目中,你要替换到其他稳定版。或者你的依赖项目中依赖了某个具有传染性协议的开源项目,但你的项目不能开源,所以你想替换到其它的实现,这时需要排除原有依赖并自己定义替换版本。
像这样
注意exclusion
的时候并不需要指定版本,因为依赖到你的项目中了,肯定只存在一个版本了,冲突已经在上面的阶段中被解决。
归类依赖
使用Spring时往往要引入很多组件,这些组件的版本号相同,如果后期你想升级依赖,需要一个一个的修改版本号。
可以通过如下方式定义一个版本常量
<properties>
<springframework.version>2.5.6</springframework.version>
</properties>
然后在后面的依赖代码中这样编写
优化依赖
使用mvn dependency:list
可以解析项目中不管是直接还是间接引用到的依赖,并且还有他们的scope
mvn dependency:tree
可以以树状结构解析,这时就可以看到它们之间的依赖关系。
mvn dependency:analyze
可以分析我们当前项目中一些依赖的问题,并生成两个报表。
我这里只有一个
Unused declared dependencies指明了编译主代码和测试代码时没有用到,但在依赖列表中的依赖,这只是参考信息,因为有的依赖需要在运行时提供,谨慎删除。
还有一个是Used undeclared dependencies,是指项目中用到的,但没有在pom.xml
中显示声明的依赖,是由其他依赖传递进来的。这也可能出现一些问题,就是你升级对应依赖的时候二级依赖也可能会被升级,但你有可能忽略这一点。
总之,这两个报表都是仅供参考,可能让你发现一些潜在的问题。