"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坐标,《搜索Maven仓库》
一文可以帮助你。
最简单的依赖如:
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>4.4</version>
-
</dependency>
<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有它的解决方案:
- <dependencies>
- <dependency>
- <groupId>org.spring.framework</groupId>
- <artifactId>spring-core</artifactId>
- <version>${spring.version}</version>
- </dependency>
- <dependency>
- <groupId>org.spring.framework</groupId>
- <artifactId>spring-beans</artifactId>
- <version>${spring.version}</version>
- </dependency>
- <dependency>
- <groupId>org.spring.framework</groupId>
- <artifactId>spring-web</artifactId>
- <version>${spring.version}</version>
- </dependency>
- <dependency>
- <groupId>org.spring.framework</groupId>
- <artifactId>spring-mock</artifactId>
- <version>${spring.version}</version>
- </dependency>
-
</dependencies>
-
<properties>
- <spring.version>2.5</spring.version>
-
</properties>
<dependencies>
<dependency>
<groupId>org.spring.framework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.spring.framework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.spring.framework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.spring.framework</groupId>
<artifactId>spring-mock</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
<properties>
<spring.version>2.5</spring.version>
</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文件打入最终的发布包也不是好事,这无谓的增加了发布包的大小。
其实我们应该这样做:
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>4.4</version>
- <scope>test</test>
-
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.4</version>
<scope>test</test>
</dependency>
于是,junit对于主源码classpath不可用,对于测试源码classpath可用,不会被打包。
再举个例子,在开发javaee应用的时候我们一定会用到servlet-api,它对于主源码和测试源码都是必要的,因为我们的代码中会引入servlet-api的包。但是,在打包的时候,将其放入WAR包就会有问题,因为web容器会提供servlet-api,如果我们再将其打包就会造成依赖冲突,解决方案如下:
- <dependency>
- <groupId>javax.servlet</groupId>
- <artifactId>servlet-api</artifactId>
- <version>2.4</version>
- <scope>provided</scope>
-
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.4</version>
<scope>provided</scope>
</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的依赖:
- <dependency>
- <groupId>org.testng</groupId>
- <artifactId>testng</artifactId>
- <version>5.7</version>
- <classifier>jdk15</classifier>
-
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>5.7</version>
<classifier>jdk15</classifier>
</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>
<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权威指南》的例子:
父模块中如此声明:
- <project>
- <modelVersion>4.0.0</modelVersion>
- <groupId>org.sonatype.mavenbook</groupId>
- <artifactId>a-parent</artifactId>
- <version>1.0.0</version>
- ...
- <dependencyManagement>
- <dependencies>
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- <version>5.1.2</version>
- </dependency>
- ...
- <dependencies>
- </dependencyManagement>
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>org.sonatype.mavenbook</groupId>
<artifactId>a-parent</artifactId>
<version>1.0.0</version>
...
<dependencyManagement>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.2</version>
</dependency>
...
<dependencies>
</dependencyManagement>
子模块中如此声明:
- <project>
- <modelVersion>4.0.0</modelVersion>
- <parent>
- <groupId>org.sonatype.mavenbook</groupId>
- <artifactId>a-parent</artifactId>
- <version>1.0.0</version>
- </parent>
- <artifactId>project-a</artifactId>
- ...
- <dependencies>
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- </dependency>
- </dependencies>
-
</project>
<project>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.sonatype.mavenbook</groupId>
<artifactId>a-parent</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>project-a</artifactId>
...
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
</project>
你依赖配置越复杂,依赖管理所起到的作用就越大,它不仅能够帮助你简化配置,它还能够帮你巩固依赖配置,也就是说,在整个项目中,对于某个构件(如mysql)的依赖配置只有一种,这样就能避免引入不同版本的依赖,避免依赖冲突。
小结
本文讲述了一些Maven依赖中重要的概念,并通过样例提供了一些最佳实践,如依赖归类,依赖范围,分类器,以及依赖管理。我的目的是通过浅显的例子讲述那些你实际工作中会需要了解的80%的内容,如果你需要更深入的了解,请参考《Maven权威指南》
。
分享到:
相关推荐
【MAVEN使用最佳实践】 Maven 是一个强大的项目管理和构建工具,它简化了Java应用程序的构建过程,通过一套约定优于配置的原则,使得项目构建、依赖管理和文档生成等工作变得标准化。以下是一些在使用Maven时可以...
8. 最佳实践: - 避免在代码中硬编码路径,依赖于Maven的约定优于配置原则。 - 保持POM简洁明了,避免过度配置。 - 使用持续集成工具(如Jenkins)与Maven结合,实现自动化构建和测试。 通过这份详尽的Maven文档...
4. **文档更新**:反向依赖信息可帮助你更新项目文档,清楚地告知用户哪些项目已经成功集成,提供示例和最佳实践。 **三、插件功能扩展** `reverse-dependencies-maven-plugin` 插件还支持一些高级功能,如: 1. ...
【Maven的依赖验证项目】是一个关于Maven项目管理和依赖...通过研究这个“Maven的依赖验证项目”,你可以掌握更多关于Maven依赖管理的实用技巧和最佳实践。记得参考文章《Maven依赖管理详解》以获取更全面的理论知识。
何为Maven 构建,依赖管理,项目信息聚合 Maven核心概念 坐标,依赖,仓库,生命周期,插件 Maven最佳实践 创建、打包、发布、版本管理 项目实战 Nexus(Todo…) M2eclipse(Todo…)
Maven 最佳实践之一个好的 parent 依赖基础 Maven 是一个流行的项目管理和构建工具,它提供了许多功能来帮助开发者管理项目依赖关系。今天,我们将讨论如何使用 parent 依赖来控制依赖版本,从而避免版本冲突。 ...
dependencyManagement 是表⽰依赖 jar 包的声明,即你在项⽬中的 dependencyManagement 下声明了依赖,maven 不会加 载该依赖,dependencyManagement 声明可以被继承。 dependencyManagement 的⼀个使⽤案例是当有⽗...
10. 使用Maven仓库的最佳实践: - 保持pom.xml文件的整洁,避免引入不必要的依赖。 - 对于公司内部的项目,推荐使用私有仓库托管,以防止版本冲突和敏感信息泄露。 - 定期更新依赖,以获取最新的安全修复和功能...
- 指导开发:Maven提供了Java项目的最佳实践,通过预设的项目结构,自动生成项目骨架。 - 自动编译:Maven不仅能自动编译源码,还能执行测试、打包、部署、文档生成等一系列任务。 - 依赖管理:Maven通过仓库管理...
**3.6 POM 最佳实践** - **3.6.1 依赖分组**:将相关的依赖放在一起,方便管理和查找。 - **3.6.2 多模块与继承** - **3.6.2.1 简单项目**:对于较小的项目,可以使用单一的 POM 文件来管理整个项目。 - **3.6....
- **版本控制**:archetype 可以随着项目需求的变化而更新,新版本的 archetype 会包含最新的最佳实践和依赖。 ### Maven Archetype 的配置文件 每个 archetype 都有一个配置文件,通常是 `archetype-metadata.xml...
**四、最佳实践** 1. **保持POM简洁**: 避免在POM中直接引入整个依赖树,尽可能精确地声明直接依赖。 2. **版本管理**: 使用`<version>`标签管理项目版本,遵循语义化版本规则。 3. **使用版本范围(Version Ranges...
Elasticsearch 5.4.0 是一个流行...在实际开发中,正确配置 Elasticsearch 5.4.0 和 X-Pack 的 Maven 依赖,可以确保项目能够顺利集成和利用 Elasticsearch 的强大功能,同时遵循最佳实践,保证系统的稳定性和安全性。
##### 最佳实践 - **版本控制**:确保项目的POM文件和所有源代码都在版本控制系统中。 - **插件管理**:合理选择和配置Maven插件,避免过多的插件导致构建过程复杂化。 - **依赖管理**:定期检查和更新项目依赖,...
在Java开发中,Maven仓库是管理项目依赖的重要工具,它可以帮助我们自动下载并管理所需的库文件...在实际开发中,还应注意版本兼容性和安全最佳实践,例如使用连接池管理数据库连接,以及及时关闭资源以避免资源泄露。
10. Maven的最佳实践: - 保持POM简洁,避免过度配置。 - 使用最新的稳定Maven版本,及时更新依赖。 - 为项目创建清晰的模块结构,便于维护和扩展。 通过阅读《Maven 完全指南》,读者可以全面了解Maven的工作...
在定义POM时,建议按照最佳实践来组织依赖和模块,比如通过分组依赖和使用多模块与继承的方式。 Maven的构建生命周期定义了项目的构建阶段和相关的生命周期目标。基本的生命周期包括clean、default和site三个阶段,...
- **Maven**:文档和社区支持同样丰富,而且由于其标准化的构建模型,更容易找到官方推荐的解决方案和最佳实践。 #### 实战案例分析 1. **错误配置示例**: - **问题描述**:在使用Maven的J2EE项目中,配置`...
通过深入学习这份"Maven帮助文档",开发者不仅可以掌握Maven的基本操作,还能理解其背后的原理和最佳实践,从而提升项目构建的效率和质量。无论你是初学者还是经验丰富的开发者,这份文档都将是你宝贵的参考资料。