Maven坐标与依赖
最近想深度学习下maven,找到一本书叫《Maven实战》,这本书讲的确实很好,唯一遗憾的是当时maven教学版本是3.0.0的,而目前已经到了3.5.4了,版本存在差距,
没关系,如果有时间和精力我也会阅读官方文档,看看到底有哪些变换。
一、坐标详解
1、何为Maven坐标
maven定义了这样一组规则:世界上任何一个构件都可以使用maven坐标唯一标识,坐标元素包括:groupId、artifactId、version、packaging、classifier。只要提供正确
的坐标就能从仓库中找到相应的构件供我们使用。maven从哪里下载构件呢?答:maven内置了一个中央仓库的地址,该中央仓库包含了世界上大部分流行的开源构件。
2、坐标详解
任何构件都必须明确定义自己的坐标,而一组maven坐标是通过一些元素定义的,他们是:groupId、artifactId、version、packaging、classifier。先看一组坐标定义如下
<groupId>org.sonatype.nexus</groupId> <artifactId>nexus-indexer</artifactId> <version>2.0.0</version> <packaging>jar</packaging>
这是nexus-indexer项目的坐标,nexus-indexer是一个对maven仓库编纂索引并提供搜索功能的类库,它是Nexus项目的一个子模块。
goupId: 定义了当前maven项目隶属的实际项目,一般是域名+项目名。比如:com.alibaba.taotao
artifactId:该元素定义实际项目中的一个maven项目(模块,一般推荐项目名+子模块名。比如:taobao-web
version: 定义maven项目当前所处版本。
packaging:定义maven项目的打包方式,默认使用jar。
classifier:该元素用来帮助定义构件输出的一些附属构件。
上述5元素groupId、artifactId、version是必须的,packaging可选,默认jar,classifier不能直接定义。同时,项目构件的文件名是与坐标对应的,
一般的规则为artificatId-version[-classifier].packaging。packing并非一定与构件扩展名对应,比如packing为maven-plugin的构件扩展名为jar。
二、依赖详解
1、依赖的配置
一般的依赖只有基本的 groupId,artifactId,version。我们来看下详细的依赖配置
<project> <dependencies> ... <dependency> <groupId>...</groupId> <artifactId>...</artifactId> <version>...</version> <type>...</type> <scope>...</scope> <optional>...</optional> <exclusions> <exclusion> ... </exclusion> </exclusions> </dependency> ... </dependencies> </project>
gourpId、artifactId、version:依赖的基本坐标,对于任何一个依赖来说,基本坐标是最重要的,maven根据坐标才能找到需要的依赖。
type:依赖的类型,对于项目坐标定义的packing,大部分情况是不必声明,默认是jar。
scope :依赖的范围 下面具体讲解
optional: 标记依赖是否可选,值为true或false,默认为false, 如果为可选依赖,则依赖不具有传递性。即B->X(可选依赖),A->B。此时A的依赖中不包含X。
exclusions:用来排除传递性依赖。
大部分依赖声明只包含基本坐标,然而在一些特殊情况下,其他元素至关重要。
2、依赖范围scope
classpath:用于指定.class文件存放的位置,类加载器会从该路径中加载所需的.class文件到内存中。maven在编译、执行测试、实际运行有着三套不同的classpath。
编译classpath:编译主代码有效
测试classpath:编译、运行测试代码有效
运行classpath:项目运行时有效
maven的依赖范围
compile:编译依赖范围。(默认方式),有效范围:编译classpath+测试classpath+运行classpath。
test:测试依赖范围。有效范围:测试classpath 比如:JUnit,只在测试时使用,在编译主代码和运行时不需要此依赖。
provided:已提供依赖范围。有效范围:编译classpath+测试classpath。
runtime:运行时依赖范围。有效范围:测试classpath+运行classpath。比如:JDBC驱动实现(mysql-connector-java)。
system:系统依赖范围。有效范围:编译classpath+测试classpath。使用system范围的依赖时必须通过systemPath元素显示地指定依赖文件的路径,
因为此类依赖不是通过maven仓库解析的,而且往往与本地及其系统绑定,可能造成构建的不可移植,慎用。systemPath元素可以引用环境变量。
<!--你引用本地的jar包,当然只能本地使用了--> <dependency> <groupId>javax.sql</groupId> <artifactId>jdbc-stdext</artifactId> <version>2.0</version> <scope>system</scope> <systemPath>${JAVA_HOME}/lib/rt.jar</systemPath> </dependency> <!--你会发现这里这里是test,那么里面有个@Test 注解 只能在test目录下有效,在main目录下该注解是无效的--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
3、传递依赖性
假设A依赖B,B依赖C,则A对于B是第一直接依赖,B对于C是第二直接依赖,A对于C是传递性依赖。第一直接依赖的范围和第二直接依赖的范围决定了传递性依赖的范围。
如下表,最左边是第一直接依赖范围,上面是第二直接依赖范围,中间交叉单元格表示传递性依赖范围。
规律:
第二直接依赖范围是compile时,传递依赖的范围与第一直接依赖范围一致。
第二直接依赖范围是test时,依赖不会传递。
第二直接依赖范围是provided时,只传递第一直接依赖为provided的依赖。
第二直接依赖范围是runtime时,传递依赖的范围与第一直接依赖范围一致,但是compile例外,此时传递依赖范围为runtime。
4、依赖调节
Maven引入的传递性依赖机制,一方面大大简化和方便了依赖声明,另一方面,大部 分情况下我们只需要关心项0的直接依赖是什么,而不用考虑这些直接依赖会引人什么传 递性依赖。
但有时候,当传递性依赖造成问题的时候,我们就需要清楚地知道该传递性依 赖是从哪条依赖路径引入的。
例如,项目A有这样的依赖关系:A->B->C->X(1.0)、A->D->X(2.0),X是A的 传递性依赖,但是两条依赖路径上有两个版本的X,那么哪个X会被Maven解析使用呢?
两个版本都被解析显然是不对的,因为那会造成依赖重复,因此必须选择一个。Maven依 赖调解(Dependency Mediation)的第一原则是:
路径最近者优先。该例中X( 1.0)的路径氏 度为3,而X(2.0)的路径长度为2,因此X(2.0)会被解折使用。
依赖调解第一原则不能解决所有问题,比如这样的依赖关系:A->B->Y(1.0)、A-> C->Y(2.0),Y(1.0)和Y(2.0)的依赖路径长度是一样的,都为2。Maven定义了依赖调解的第二原则:
第一声明者优先。在依 赖路径长度相等的前提下,在POM中依赖声明的顺序决定了谁会被解析使用,顺序最前的那个依赖优胜。该例中,如果B的依赖声明在C之前,那么Y (1.0)就会被解析使用.
5、排除依赖
传递性依赖会给项目引入很多依赖,简化项目依赖管理,但是也会带来问题。
需求:比如当前项目有一个第三方依赖,而第三方依赖依赖了另一个类库的SNAPSHOT版本,那么这个SNAPSHOT就会成为当前项目的传递性依赖,而SNAPSHOT的不稳定性会直接影响到当前项目。
这时候就应该排除掉SNAPSHOT。并且声明该类库的正式发布版本。
<dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>3.6.10.Final</version> <exclusions> <exclusion> <groupId>slf4j-api</groupId> <artifactId>slf4j-api</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.12</version> </dependency>
上述代码中hibernate-validator依赖slf4j-api,但是当前项目不想使用传递过来的slf4j-api,所以通过exclusions声明排除依赖。当前项目声明了自己需要的1.7.12版本的slf4j-api。
exclusions可以包含一个或者多fexdWon子元素,因此可以排除一个或者多 个传递性依赖。需要注意的是,声明exclusion的时候只需要groupld和artifactld,而不 笛要version元素,
这是因为只需要gmupkl和arlifactid就能唯一定位依赖图中的某个依 赖。换句话说,Maven解析后的依赖中,不可能出现groupW和artifactld相同,但是 version不同的两个依赖。
注意:排除依赖的时候也要注意,比如A依赖1.1版本的B, ,而你不想要1.1版本的B,而是要2.1的B,这个时候也需要考虑A跟2.1的B是否能兼容。
6、归类依赖
需求:关于springframework的依赖有好多,org.springframework:spirng-core:2.5.6、org.springframework:sprng-beans:2.5.6、org.springframework:spring-context:2.5.6,
他们来自同一个项目不同模块,因此版本都是相同的,可以预见在升级spring时这些依赖都会一起升级,为了方便统一所以使用properties元素定义maven属性。
<project> .... <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <springframework.version>4.3.2.RELEASE</springframework.version> </properties> <!--通过使用${springframework.version}替换掉实际值,将所有spring依赖的版本值都使用这一引用值表示。--> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${springframework.version}</version> </dependency> </dependencies> ... </project>
参考
《Maven实战》. 许晓斌
同时很高兴,找到看过这本书的人的总结。
坐标详解:https://www.jianshu.com/p/30ece967dccd
依赖配置:https://www.jianshu.com/p/79cf4423281a
我只是偶尔安静下来,对过去的种种思忖一番。那些曾经的旧时光里即便有过天真愚钝,也不值得谴责。毕竟,往后的日子,还很长。不断鼓励自己,
天一亮,又是崭新的起点,又是未知的征程(上校20)