Java构建工具:如何用Maven,Gradle和Ant+Ivy进行依赖管理

原文来自:https://zeroturnaround.com/rebellabs/java-build-tools-how-dependency-management-works-with-maven-gradle-and-ant-ivy/

编译的时候可以运行,运行时出问题

在当今java项目自动化构建的场景当中,依赖管理已经成为了项目构建自动化工具中的一个主要部分,但是在过去并总是这样。

回想以前那个很爽的时候,你的项目依赖性管理只需要将jar包导入到lib文件夹中,然后将其加入到你的版本控制系统中。

或者可以采取更冒险的方式,你可以写一个从外部源中下载想要的版本的库文件的脚本,还要向神仙保佑外部的URL不会改变。

用手动的方式来完成所有的这些将成为一个令人怯步而且麻烦的过程,尤其是考虑到透明的依赖(依赖的依赖),也许只有在某个库文件的代码执行的时候才会出现。

这会造成在运行过程中发生某些意向不到的麻烦,即使是在编译过程能够修复的问题也会发现和解析出有透明依赖。

幸运的是,在如今的项目构建自动化工具中这种情形不再是主要问题(这句话翻得有问题)。

项目的模块化变得越来越受欢迎,内部项目和外部依赖的需求也增加了,结果所有常用的项目自动化构建工具针对这种挑战进行加强了并且支持依赖性管理;不论是out of box还是通过插件。

为了更加方便开发者,它们都是用相同的语法来定义依赖性,同时也可以从相同的公共的资料库(public artifact repositories)中抽取依赖性关系(比如Maven Central)。

但是和这些公共的资料库一样方便的同时,它们也为项目带来了复杂性;透明依赖的冲突,而且增加了对于远程库的依赖。

如何定义依赖性

用于定义依赖性最常用的语法是加入group-id,artifact-id和需要的版本号到构建脚本的依赖性部分。项目构建工具会试图解析这些依赖性,通过在它们的局部环境中和远程定义的库中。

在下面的例子中,将会向你展示如何Google的公共资源库Guava的依赖性。在开始之前,你得知道它的group-id(com.google.guava),它的artifact-id(guava),和你感兴趣的库的版本号,

(在这里以最近的版本“15.0”为例)。手上有了这些信息之后,你可以往构建脚本的依赖性部分加入这些依赖。

以下是在Maven,Gradle和Ant Ivy中的示例:

Maven

Gradle

Ant+Ivy(ivy.xml,或者在build.xml中)

现在你可以知道之前的参数是如何在这三个例子中使用的,在Maven中可能稍显累赘一点。

在构建工具中,property的名称可能会有些许不同,但大体上还是类似的。

如何使用版本范围

What if you don’t want to depend on a specific version of a library, but know that any version in a specific range will do? Or if you know that any version except ‘X’ will work? This is where version ranges can come in handy.

Instead of just a single version number, you can also specify a range, using a notation very similar to how you would define an interval in mathematics: parenthesis ( ) work as exclusive markers and brackets [ ] as inclusive markers. Using a range allows the tool to pick a suitable version for the build. Continuing the example with Guava, if you know that any version between 12.0 and up to 15.0 (excluded) would work, you could define it as “[12.0,15.0)”. You can also leave it open-ended like “[12.0,)”, which adds a requirement for any version from 12.0 and up, similarly “(,12.0]” would be any version up to and including 12.0. Or if you want anything higher than 12.0, except for version 13.0, you could specify “[12.0,13.0),(13.0,)” as the version.

But why use ranges at all? It’s an easy and convenient way to get a newer version of the library without having to change your build script; however, it also sets you up for potential trouble, should the author of the library opt to change functionality or the API that you’re relying on! Another caveat about using ranges is that if the version numbering is inconsistent or doesn’t follow some standard, things might not go as expected. Using ranges on artifacts with qualifiers in the version string (like SNAPSHOT, ALPHA, BETA etc) also doesn’t always go as expected, as the range definition only supports numerical intervals and the build tool might pick a beta version because it has a higher number than the release version.

Besides ranges and specific versions, dependencies can also be resolved using dynamic versions by using the keywords ‘LATEST’ or ‘RELEASE’ instead of the version number. Using those will make the build tool inquire the artifact repository about which version is the latest (or latest release) and use that version. The same caveats apply here as with version ranges though–any changes to the API or functionality might break the world.

Transitive dependencies and dependency conflicts

Let’s go back in time to the “good old days” again. Here, you had full control and an overview over which libraries were used, since it was immediately visible which libraries were present in the lib-folder. But with declarative dependencies remotely available, and the transitive dependencies that are automatically included as well, this easy overview of which libraries are in use have become somewhat obscured. Luckily, most build tools have a plugin or an option to list the entire dependency tree:

  • Maven: mvn dependency:tree -Dverbose

  • Gradle: gradle -q dependencies
  • Ivy: <report conf="compile" />

Ivy’s report option allows you to generate the reports in various different formats, the default being an HTML and graphml report; so not as straight-forward as the console output you get from Maven and Gradle.

But what happens if there are conflicts in the dependencies? What if Library A and Library B both depend on Library C, but require different versions of it? This is where things start to get a bit tricky, and where build tools’ dependency management implementations diverge.

Assuming we have a project dependency structure that looks something like this:

  • Project

    • Module A

      • com.google.guava:guava:[11.0,12.99]
    • Module B
      • com.google.guava:guava:[13.0,)

Trying to build the above project with Maven will result in an error because the dependencies could not be resolved, since no version of Guava can satisfy both ranges. But if you use an equivalent Gradle build script and build it with Gradle, it will pick the highest version of Guava available in either of the ranges; which in this case means version ’15.0’. Changing the dependency of Module B, so its range is ‘[12.0,)’, Maven will now pick version ‘12.0.1’, which satisfies both ranges; Gradle still picks version ’15.0’.

Ivy and Gradle acts very similar in these scenarios, which isn’t that surprising considering Gradle originally used Ivy as their underlying dependency management implementation until they implemented their own dependency resolution engine.

The usage of ranges isn’t that widely-used though, and the more common use case is to just have the simple version number listed in the dependency. Even in this simple case, Maven, Gradle and Ivy again act vastly different when resolving dependencies!

Maven utilizes a “nearest definition” strategy, in which the closest version to the root is the version it’ll use throughout the entire build! In the case of the structure above, Module A and Module B both depend on Guava, and they are both found at the same depth; but since Module A is listed in the project before Module B, the dependency for Guava used there will be the version used for the entire build, even if the latter relies on a higher version!

Due to this, a common approach for having better control over which version of conflicting dependencies is used is to add a dependency to the wanted version in the parent pom file. Since this is the first pom to be parsed, the dependencies listed there will always be nearest, thus Maven should use that version and ignore every other version mentioned in the dependency graph.

As opposed to Maven, both Gradle and Ivy by default resolve dependency conflicts with a simple strategy: they just select the highest version :) If this strategy doesn’t suit your needs, you can select a different strategy: for instance, force the build to fail should a dependency conflict arise, or forcing a specific version of the dependency to be used, overriding any version otherwise defined as part of the dependency graph.

Final words

While the above was just a short introduction to some of the great and not so great things about dependency management, these are some of the things to keep in mind when dealing with it, and a good excuse to start reading up on the documentation for your build tool to see exactly what’s happening behind the screen.

Even though keeping binary dependencies in your VCS is somewhat frowned upon today, the idea of having complete control over which libraries you include is still a good idea. No matter which build tools you prefer (or are required to use), it’s always a good idea to know how they handle your dependencies; an old version of a library might introduce bugs, strangeness or, in the worst case, security risks to your production system! Keeping a handle on this can potentially save you a lot of headaches down the road.

时间: 2024-10-18 01:44:27

Java构建工具:如何用Maven,Gradle和Ant+Ivy进行依赖管理的相关文章

Java构建工具Ant小记(一)

Ant简介 Ant是基于java的构建工具.理论上来说它类似与make工具,但是却克服了make的一些固有的缺陷. 传统的Make是基于操作系统shell的构建工具,虽然也可以基于工作的os对make工具进行扩展,但却难以实现跨平台构建.Ant基于java扩展功能,并且通过在xml中的target中定义的task进行构建任务的定义.其中每一个任务都是实现特定任务接口的类.同时Ant也提供了exec任务允许调用不同的操作系统的shell命令. Ant主要元素介绍 Ant使用xml文件定义构建过程,

Java构建工具Ant之在Windows下配置环境变量

配置环境变量这个东西应该要烂在程序猿,基本上Windows或者Mas osx还是Linux都有固定的配置方式,我们在使用任何软件都是基于系统这个最高层的应用程序上的,前面笔者已经介绍过如何在Max os配置java环境变量,这里不再赘述. 计算机->右键->属性 定义系统变量ANT_HOME 指定变量值"你的ant的安装目录" 跟Java环境变量配置方法一样,如果指定了ant环境变量之后,就可以在cmd命令行下使用ant提供的命令:ant -version 输出ant当前版

Gradle 构建工具

参考文章: 作者:ghui 链接:https://www.zhihu.com/question/30432152/answer/48239946 来源:知乎 著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. Gradle是一个基于Apache Ant和Apache Maven概念的项目自动化建构工具.它使用一种基于Groovy的特定领域语言来声明项目设置,而不是传统的XML.当前其支持的语言限于Java.Groovy和Scala,计划未来将支持更多的语言. 上面是维基上对Gr

JAVA生成(可执行)Jar包的全面详解说明 [打包][SpringBoot][Eclipse][IDEA][Maven][Gradle][分离][可执行]

辛苦所得,转载还请注明: https://www.cnblogs.com/applerosa/p/9739007.html  得空整理了关于java 开发中,所有打包方式的 一个操作方法, 有基于IDE的,有基于构建工具的. 这里还是比较建议新手朋友尽快习惯 maven 和 gradle 等构建工具自带的打包方式. 不是说逼格高,的确是因为不依赖 IDE, 配置好 一两行命令就搞定. 离开IDE 照样出包. 大概分为这几个步骤 一.  关于Jar 包(example.jar) 的 结构/作用/使

前端工程的构建工具对比 Gulp vs Grunt

1. Grunt -> Gulp 早些年提到构建工具,难免会让人联想到历史比较悠久的Make,Ant,以及后来为了更方便的构建结构类似的Java项目而出现的Maven.Node催生了一批自动化工具,像Bower,Yeoman,Grunt等.而如今前端提到构建工具会自然想起Grunt.Java世界里的Maven提供了强大的包依赖管理和构建生命周期管理. 在JavaScript的世界里,Grunt.js是基于Node.js的自动化任务运行器.2013年02月18日,Grunt v0.4.0 发布.F

Gulp vs Grunt 前端构建工具对比

Gulp vs Grunt 前端工程的构建工具对比 1. Grunt -> Gulp 早些年提到构建工具,难免会让人联想到历史比较悠久的Make,Ant,以及后来为了更方便的构建结构类似的Java项目而出现的Maven.Node催生了一批自动化工具,像Bower,Yeoman,Grunt等.而如今前端提到构建工具会自然想起Grunt.Java世界里的Maven提供了强大的包依赖管理和构建生命周期管理. 在JavaScript的世界里,Grunt.js是基于Node.js的自动化任务运行器.201

build tool(构建工具)

什么是build tool? 构建工具是从源代码自动创建可执行应用程序的程序.构建包括将代码编译,链接和打包成可用或可执行的形式.在小项目中,开发人员通常会手动调用构建过程.这对于较大的项目来说是不实际的,在这些项目中,很难跟踪需要构建的内容,构建过程中的顺序和依赖关系.使用自动化工具可以使构建过程更加一致.基本上构建的自动化是编写或使一大部分任务自动执行的一个动作,而这些任务则是软件开发者的日常,像是: 1.下载依赖 2.将源代码编译成二进制代码 3.打包生成的二进制代码 4.进行单元测试 5

grunt构建工具

前端集成解决方案 解决一系列问题,比如模块化,MVC,打包等 主流前端解决方案 Yeoman bower grunt | gulp 学习前端自动化构建工具前提 NodeJs npm  node包管理,分发工具  相当于php的pear,是个命令行工具 在ubuntu安装node,npm 通过 apt-get 方式 sudo apt-get install nodejs ; sudo apt-get install npm; 检查版本 nodejs -v       npm -v Yeoman的安

构建一个简单的Maven项目

这里用Maven Archetype插件从空白开始创建简单的项目. 熟悉Maven 项目和Maven的核心概念. 关键词:构建生命周期(build lifecycle), Maven仓库(repositories), 依赖管理(dependency management), 项目对象模型(project object model)