Freewind @ Thoughtworks scala java javascript dart 工具 编程实践 月结 math python english [comments admin] [feed]

(2015-04-30) Maven的依赖管理

广告: 云梯:翻墙vpn (省10元) 土行孙:科研用户翻墙http proxy (有优惠)

因为项目需要,需要研究清楚Maven中的依赖管理是怎么工作的,今天花了一天时间看了相关的文档,一些以前一直没搞清楚的问题,这次终于弄清楚了。

在继续写之前,让我先吐一会儿。前两天看了Gradle,今天再看Maven,只能感叹,人们不选Gradle真是没有天理。(下面总结时会跟gradle进行对比,方便理解)

文档的对比

  1. Gradle的文档非常美观,内容详细、清楚,示例很多,而且都是精心挑选的,让一个不熟悉的人也可以快速看懂 https://gradle.org/docs/current/userguide/dependency_management.html
  2. Maven的文档丑,内容简陋,例子少,经常遇到一些看不懂的术语,但没有或者只有极少的解释
    https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html (这还算是maven中写得比较好的一篇)

声明依赖的语法对比

Gradle

dependencies {
    compile("group-a:artifact-a:1.0") {
        exclude group: 'group-c', module: 'excluded-artifact'
    }
    runtime "group-a:artifact-b:1.0@bar"
}

(注:当exclude一个由maven生成的依赖时,module对应artifactId)

Maven

<project>
  <dependencies>
    <dependency>
      <groupId>group-a</groupId>
      <artifactId>artifact-a</artifactId>
      <version>1.0</version>
      <exclusions>
        <exclusion>
          <groupId>group-c</groupId>
          <artifactId>excluded-artifact</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>group-a</groupId>
      <artifactId>artifact-b</artifactId>
      <version>1.0</version>
      <type>bar</type>
      <scope>runtime</scope>
    </dependency>
  </dependencies>
</project>

两者的内容是一样的,但是Maven这边,如果不借助编辑器的高亮,基本上没法看啊,眼都瞎了。

版本冲突如何解决

假设在项目中,不论是自己声明的,或者是使用的某个第三方库依赖的,如果有同名但不同版本的情况出现,就会产生版本冲突。

比如,自己声明使用log4j 1.4,但某个依赖使用了log4j 1.5,另一个使用了log4j 1.3,我们不能同时把这三个包都加到classpath里,否则就会出现无法预料的情况,因为你很难搞清楚jvm到底用的是哪个。

各种构建工具都有自己的确定最终使用版本的方式。Gradle默认采用最大值,不论是谁声明的,直接取最大值作为最终采用的版本。

而Maven使用了一种叫nearest definition的方式。Maven把我们自己显式声明的依赖称为第一层依赖,它们自己的依赖叫第二层,还有第三层,更多层。当发现版本冲突的时候,优先采用层数小的,忽略后面的。

比如项目中有依赖A -> B -> C 1.8A -> C 1.7,那么maven就会采用C 1.7,因为它比较近,层数小。

但是如果冲突出现在同一层,比如A -> C 1.8B -> C 1.7,那么在maven 2.0.8之前,它是没有办法决定使用哪一个(报错还是随便选不清楚),直到在2.0.9时才确定会使用它先遇到的那个。

个人还是比较喜欢gradle的方式,符合直觉,更好理解。

maven中这种还需要数层,还可能造成麻烦:一旦升级了某个依赖后发现它必须使用一个更加新的版本比如C 1.7时,发现比它层数小的某个依赖,使用了C 1.6,那么最后maven就会采用C 1.6,这样就可能出错。这时我们就必须显式声明对C 1.7的依赖。但这会造成理解上的误会,因为通常我们声明的依赖只应该是我们代码中直接依赖的库。

有一篇很有意思的文章来讲解这个主题:http://mrhaki.blogspot.jp/2014/08/gradle-goodness-getting-more-dependency.html

Dependency Scope

在maven中预先定义了几种scope,规定了它们的含义,之间的依赖关系和使用场景。

这些名字和使用场景是写死的,我们不能自行扩展。与之相对的是,在另一个依赖管理工具Ivy中,允许人们自定义这些scope(Ivy中的叫法是configuration),Gradle中更倾向于Ivy的做法。

有一些scope我们经常使用,比如compile, runtime, test, provided,而systemimport见得比较少。

system

这个好像是说必须由你自己提供或者指明位置的依赖,通常用来指明JDK或者虚拟机提供的依赖,比如rt.jar, tools.jar等。

对于这种依赖,maven不会到仓库上去找

import

import是在maven 2.0.9之后提供的功能,主要是方便我们可以先把常用的或者需要共享的一些依赖信息先写在某个文件,然后在其它的POM文件中,直接引用它们,少写点版本之类的信息,增强一致性。

在之前的版本中,这个功能可以通过parent标签来实现,即提供一个parent POM包含这些信息,然后在其它的POM中继承它。但是这种方式有一个严重的问题,即每个POM只能有一个parent POM,对于复杂的情况,不能强迫所有的POM都继承于同一个parent吧。

现在有了import,就可以让我们随意导入另一个POM中定义的依赖,不需要继承。

如果有版本冲突,是按全部导入后的情况来计算的。

依赖的type

每个依赖有多种type可选,比如默认的jar,还可以有war, zip, pom或者自定义的类型。

在gradle中,可以使用group-a:artifact-b:1.0@zip的方式,而在maven中:

<dependency>
      <groupId>group-a</groupId>
      <artifactId>artifact-b</artifactId>
      <version>1.0</version>
      <type>zip</type>
    </dependency>

https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html

comments powered by Disqus