maven 实践 :管理依赖

摘要:
有些人认为Maven是一个依赖管理工具。当然,这种想法是错误的,但Maven也有理由给人这样的印象,即这种错误是造成的。因为Maven的依赖性管理非常强大。使用Maven,您不再需要面对大量jar问题,如大头、依赖冲突和无用的依赖,以有效地预防和解决它们。本节介绍如何充分利用Maven的依赖关系管理。

有人认为Maven是一个依赖管理工具,当然这种想法是错误的(确切的说Maven是一个项目管理工具,贯穿了整个项目生命周期,编译,测试,打包,发布...),但Maven给人造成这种错误的印象也是有原因的,因为Maven的依赖管理十分强大,用好了Maven,你不再需要面对一大堆jar感到头大,依赖冲突,无用依赖等问题也能够得到有效的防止和解决。本节介绍如何用好Maven的依赖管理。

最简单的依赖

依赖是使用Maven坐标来定位的,而Maven坐标主要由GAV(groupId, artifactId, version)构成。因此,使用任何一个依赖之间,你都需要知道它的Maven坐标,关于如何寻找Maven坐标,《搜索Maven仓库》 一文可以帮助你。

最简单的依赖如:

    <dependency>  
      <groupId>junit</groupId>  
      <artifactId>junit</artifactId>  
      <version>4.4</version>  
    </dependency>  

上例中我们声明了一个对junit的依赖,它的groupId是junit, artifactId是junit, version是4.4。这一组GAV构成了一个Maven坐标,基于此,Maven就能在本地或者远程仓库中找到对应的junit-4.4.jar文件。

依赖归类

随着项目的增大,你的依赖越来越多,比如说你依赖了一堆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有它的解决方案:

 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文件打入最终的发布包也不是好事,这无谓的增加了发布包的大小。

其实我们应该这样做:

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,如果我们再将其打包就会造 成依赖冲突,

解决方案如下:

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 缺省值TRUETRUETRUE
testFALSETRUEFALSE
runtimeFALSETRUETRUE
providedTRUETRUEFALSE

需要注意的是,当我们没有声明依赖范围的时候,其默认的依赖范围是compile。

分类器(classifer)

GAV是Maven坐标最基本最重要的组成部分,但GAV不是全部。还有一个元素叫做分类器(classifier),90%的情况你不会用到它,但有些时候,分类器非常不可或缺。

举个简单的例子,当我们需要依赖TestNG的时候,简单的声明GAV会出错,因为TestNG强制需要你提供分类器,以区别jdk14和jdk15,

我们需要这样声明对TestNG的依赖:

1     <dependency>  
2       <groupId>org.testng</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构件:

    <dependency>  
      <groupId>org.myorg.myapp</groupId>  
      <artifactId>core</artifactId>  
      <version>${project.version}</version>  
      <classifier>test</classifier>  
    </dependency>  

理解了分类器,那么可供依赖的资源就变得更加丰富。

依赖管理(dependencyManagement)

当你只有一个Maven模块的时候,你完全不需要看这个部分。但你心里应该清楚,只有一个Maven模块的项目基本上只是个玩具。

实际的项目中,你会有一大把的Maven模块,而且你往往发现这些模块有很多依赖是完全项目的,A模块有个对spring的依赖,B模块也有,它们 的依赖配置一模一样,同样的groupId, artifactId, version,或者还有exclusions, classifer。细心的分会发现这是一种重复,重复就意味着潜在的问题,Maven提供的dependencyManagement就是用来消除这种 重复的。

正确的做法是:

1. 在父模块中使用dependencyManagement配置依赖

2. 在子模块中使用dependencies添加依赖

dependencyManagement实际上不会真正引入任何依赖,dependencies才会。但是,当父模块中配置了某个依赖之后,子模块只需使用简单groupId和artifactId就能自动继承相应的父模块依赖配置。

这里是一个来自于《Maven权威指南》的例子:

父模块中如此声明:

 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>  

子模块中如此声明:

 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%的内容,如果你需要更深入的了解,请参考《Maven权威指南》

免责声明:文章转载自《maven 实践 :管理依赖》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇支付宝支付(三)—APP支付(alipay.trade.app.pay)微信小程序实战:表单与选择控件的结合下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Java单元测试 Http Server Mock框架选型

背景动机 某期优化需要针对通用的HttpClient封装组件--HttpExecutor在保证上层暴露API不动的前提做较多改动,大致包括以下几点: apache http client 版本升级 HttpClientBuilder代码重构 RequestBuilder代码重构 自定义RetryHandler HttpContext扩展 自定义HttpR...

Mockito (一)

Mocktio 入门 Mockito 是一个模拟测试框架。主要功能是模拟类/对象的行为。 Mockito 一般用于控制调用外部的返回值,让我们只关心和测试自己的业务逻辑。 我们看一个示例: package demo; import java.util.Random; public class HttpService { public int...

代价敏感学习初探

1. 代价敏感学习简介 0x1:如何将业务场景中对FP和FN损失的不同接受程度,调整我们的损失函数 1. 什么场景下需要代码敏感学习 在很多真实业务场景中,包括笔者所在的网络安全领域,误报造成的损失常常比漏报来的要大,原因很简单,如果一个IDS系统每天都在产生大量虚警,那么对事件响应处理人员的压力就会非常大,时间久了,大家对IDS的信任度就会下降,同时真实...

机器学习ROC图解读

1. 分类器评估指标 对于二分类问题,可将样例根据其真实类别和分类器预测类别划分为:真正例(True Positive,TP):真实类别为正例,预测类别为正例。假正例(False Positive,FP):真实类别为负例,预测类别为正例。假负例(False Negative,FN):真实类别为正例,预测类别为负例。真负例(True Negative,TN)...

Bagging和Boosting 概念及区别

Bagging和Boosting都是将已有的分类或回归算法通过一定方式组合起来,形成一个性能更加强大的分类器,更准确的说这是一种分类算法的组装方法。即将弱分类器组装成强分类器的方法。 首先介绍Bootstraping,即自助法:它是一种有放回的抽样方法(可能抽到重复的样本)。 1、Bagging (bootstrap aggregating) Baggin...

【JUnit】JUnit 父类和子类执行顺序

环境 JDK 6 JUnit 4.13 Spring Tool Suite 4.6.2 Maven 3.6.3 顺序注解 可以参与到执行过程中的注解一共有四个,分别是: @BeforeClass:在当前测试类的所有测试执行之前执行,比 @Before 更早执行。 @AfterClass:在当前测试类的所有测试执行之后执行,比 @After 更晚执行。...