Maven最佳实践:管理依赖
Maven最佳实践:管理依赖
"If I have seen further it is by standing on the shoulders of Giants" —— Isaac Newton (1642-1727)
有⼈认为Maven是⼀个依赖管理⼯具,当然这种想法是错误的(确切的说Maven是⼀个项⽬管理⼯具,贯穿了整个项⽬⽣命周期,编译,测试,打包,发布...),但Maven给⼈造成这种错误的印象也是有原因的,因为Maven的依赖管理⼗分强⼤,⽤好了Maven,你不再需要⾯对⼀⼤堆jar感到头⼤,依赖冲突,⽆⽤依赖等问题也能够得到有效的防⽌和解决。本节介绍如何⽤好Maven的依赖管理。
最简单的依赖
依赖是使⽤Maven坐标来定位的,⽽Maven坐标主要由GAV(groupId, artifactId, version)构成。因此,使⽤任何⼀个依赖之间,你都需要知道它的Maven坐标,关于如何寻Maven坐标,⼀⽂可以帮助你。
最简单的依赖如:
Xml代码
1<dependency>
2<groupId>junit</groupId>
3<artifactId>junit</artifactId>
4<version>4.4</version>
5</dependency>
上例中我们声明了⼀个对junit的依赖,它的groupId是junit, artifactId是junit, version是4.4。这⼀组GAV构成了⼀个Maven坐标,基于
此,Maven就能在本地或者远程仓库中到对应的junit-4.4.jar⽂件。
spring framework版本依赖归类
随着项⽬的增⼤,你的依赖越来越多,⽐如说你依赖了⼀堆spring的jar,有org.spring.framework:spring-core, org.spring.framework:beans, org.spring.framework:spring-web, org.spring.framework:spring-mock。它们的groupId是相同的,artifactId不同。为了管理其版本,你对它们进⾏过统⼀的升级,逐个的将version改成了最新版。但是,显然,当POM很⼤的时候你说不定会犯错误,⽽当版本不⼀致的时候,⼀些诡异的兼容性问题就可能出现。
对此,Maven有它的解决⽅案:
Xml代码
1<dependencies>
2<dependency>
3<groupId>org.spring.framework</groupId>
4<artifactId>spring-core</artifactId>
5<version>${spring.version}</version>
6</dependency>
7<dependency>
8<groupId>org.spring.framework</groupId>
9<artifactId>spring-beans</artifactId>
10<version>${spring.version}</version>
11</dependency>
12<dependency>
13<groupId>org.spring.framework</groupId>
14<artifactId>spring-web</artifactId>
15<version>${spring.version}</version>
16</dependency>
17<dependency>
18<groupId>org.spring.framework</groupId>
19<artifactId>spring-mock</artifactId>
20<version>${spring.version}</version>
21</dependency>
22</dependencies>
23
24<properties>
25<spring.version>2.5</spring.version>
26</properties>
这⾥我们定义了⼀个Maven属性,其名称为spring.version,值是2.5。在这个POM中,我们就能⽤${spring.version}的⽅式来引⽤该属性。我们看到,所有spring相关的依赖的version元素现在都成了${spring.version},当Maven运⾏的时候,它会⾃动⽤值2.5来替换这个引⽤。
当我们需要升级spring的时候,只要更改⼀个地⽅便可,⽽且,你现在能很⾼的保证所有的spring依赖包都是同⼀个版本。
依赖范围(scope)
本⽂的第⼀个例⼦其实是有漏洞的,对于Junit,⼀般来说你只有在运⾏测试的时候需要它,也就是说,它对于src/main/java的classpath没什么意义,并且,将Junit的jar⽂件打⼊最终的发布包也不是好事,这⽆谓的增加了发布包的⼤⼩。
其实我们应该这样做:
Xml代码
1<dependency>
2<groupId>junit</groupId>
3<artifactId>junit</artifactId>
4<version>4.4</version>
5<scope>test</test>
6</dependency>
于是,junit对于主源码classpath不可⽤,对于测试源码classpath可⽤,不会被打包。
再举个例⼦,在开发javaee应⽤的时候我们⼀定会⽤到servlet-api,它对于主源码和测试源码都是必要的,因为我们的代码中会引⼊servlet-api的包。但是,在打包的时候,将其放⼊WAR包就会有问题,因为web容器会提供servlet-api,如果我们再将其打包就会造成依赖冲突,解决⽅案如下:
Xml代码
1<dependency>
2<groupId>javax.servlet</groupId>
3<artifactId>servlet-api</artifactId>
4<version>2.4</version>
5<scope>provided</scope>
6</dependency>
将依赖范围设置成provided,就意味着该依赖对于主源码classpath,以及测试classpath可⽤,但不会被打包。这正是servlet-api所需要的。
这⾥归纳⼀下主要的依赖范围以及作⽤:
依赖范围(scope)主源码classpath可⽤测试源码classpath可⽤会被打包
compile 缺省值TRUE TRUE TRUE
test FALSE TRUE FALSE
runtime FALSE TRUE TRUE
provided TRUE TRUE FALSE
需要注意的是,当我们没有声明依赖范围的时候,其默认的依赖范围是compile。
分类器(classifer)
GAV是Maven坐标最基本最重要的组成部分,但GAV不是全部。还有⼀个元素叫做分类器(classifier),90%的情况你不会⽤到它,但有些时候,分类器⾮常不可或缺。
举个简单的例⼦,当我们需要依赖TestNG的时候,简单的声明GAV会出错,因为TestNG强制需要你提供分类器,以区别jdk14和jdk15,我们需要这样声明对TestNG的依赖:
Xml代码
1<dependency>
2<groupId&stng</groupId>
3<artifactId>testng</artifactId>
4<version>5.7</version>
5<classifier>jdk15</classifier>
6</dependency>
你会注意到maven下载了⼀个名为testng-5.7-jdk15.jar的⽂件。其命名模式实际上是<artifactId>-<version>-<classifier>.<packaging>。理解了这个模式以后,你就会发现很多⽂件其实都是默认构件的分类器扩展,如 myapp-1.0-test.jar, myapp-1.0-sources.jar。
分类器还有⼀个⾮常有⽤的⽤途是:我们可以⽤它来声明对test构件的依赖,⽐如,我们在⼀个核⼼模块的src/test/java中声明了⼀些基础类,然后我们发现这些测试基础类对于很多其它模块的测试类都有⽤。
没有分类器,我们是没有办法去依赖src/test/java中的内容的,因为这些内容不会被打包到主构件中,它们单独的被打包成⼀个模式为<artifactId>-<version>-test.jar的⽂件。
我们可以使⽤分类器来依赖这样的test构件:
Xml代码
1<dependency>
2<groupId&myapp</groupId>
3<artifactId>core</artifactId>
4<version>${project.version}</version>
5<classifier>test</classifier>
6</dependency>
理解了分类器,那么可供依赖的资源就变得更加丰富。
依赖管理(dependencyManagement)
当你只有⼀个Maven模块的时候,你完全不需要看这个部分。但你⼼⾥应该清楚,只有⼀个Maven模块的项⽬基本上只是个玩具。
实际的项⽬中,你会有⼀⼤把的Maven模块,⽽且你往往发现这些模块有很多依赖是完全项⽬的,A模块有个对spring的依赖,B模块也有,它们的依赖配置⼀模⼀样,同样的groupId, artifactId, version,或者还有exclusions, classifer。细⼼的分会发现这是⼀种重复,重复就意味着潜在的问题,Maven提供的dependencyManagement就是⽤来消除这种重复的。
正确的做法是:
1. 在⽗模块中使⽤dependencyManagement配置依赖
2. 在⼦模块中使⽤dependencies添加依赖
dependencyManagement实际上不会真正引⼊任何依赖,dependencies才会。但是,当⽗模块中配置了某个依赖之后,⼦模块只需使⽤简单groupId和artifactId就能⾃动继承相应的⽗模块依赖配置。
这⾥是⼀个来⾃于《Maven权威指南》的例⼦:
⽗模块中如此声明:
Xml代码
1<project>
2<modelVersion>4.0.0</modelVersion>
3<groupId>org.sonatype.mavenbook</groupId>
4<artifactId>a-parent</artifactId>
5<version>1.0.0</version>
6  ...
7<dependencyManagement>
8<dependencies>
9<dependency>
10<groupId>mysql</groupId>
11<artifactId>mysql-connector-java</artifactId>
12<version>5.1.2</version>
13</dependency>
14      ...
15<dependencies>
16</dependencyManagement>
⼦模块中如此声明:
Xml代码
1<project>
2<modelVersion>4.0.0</modelVersion>
3<parent>
4<groupId>org.sonatype.mavenbook</groupId>
5<artifactId>a-parent</artifactId>
6<version>1.0.0</version>
7</parent>
8<artifactId>project-a</artifactId>
9  ...
10<dependencies>
11<dependency>
12<groupId>mysql</groupId>
13<artifactId>mysql-connector-java</artifactId>
14</dependency>
15</dependencies>
16</project>
你依赖配置越复杂,依赖管理所起到的作⽤就越⼤,它不仅能够帮助你简化配置,它还能够帮你巩固依赖配置,也就是说,在整个项⽬中,对于某个构件(如mysql)的依赖配置只有⼀种,这样就能避免引⼊不同版本的依赖,避免依赖冲突。
⼩结
本⽂讲述了⼀些Maven依赖中重要的概念,并通过样例提供了⼀些最佳实践,如依赖归类,依赖范围,分类器,以及依赖管理。我的⽬的是通过浅显的例⼦讲述那些你实际⼯作中会需要了解的80%的内容,如果你需要更深⼊的了解,请参考。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。