Gradle 能否以任何方式帮助解决 jar hell?

Can Gradle help solve jar hell in any way?

Java这里8个。

假设有一个旧版本的 widget 库,Maven 坐标 widgetmakers:widget:1.0.4,其中定义了一个 class,如下所示:

public class Widget {
    private String meow;

    // constructor, getters, setters, etc.
}

岁月流逝。这个 widget 库的维护者决定 Widget 永远不应该 meow,而是实际上应该 bark。因此发布了一个新版本,Maven 坐标 widgetmakers:widget:2.0.0Widget 看起来像:

public class Widget {
    private Bark bark;

    // constructor, getters, setters, etc.
}

所以现在我要构建我的应用程序,myapp。而且,想要使用我所有依赖项的最新稳定版本,我这样声明我的依赖项(在 build.gradle 内):

dependencies {
    compile (
        ,'org.slf4j:slf4j-api:1.7.20'
        ,'org.slf4j:slf4j-simple:1.7.20'
        ,'bupo:fizzbuzz:3.7.14'
        ,'commons-cli:commons-cli:1.2'
        ,'widgetmakers:widget:2.0.0'
    )
}

现在假设这个(虚构的)fizzbuzz 总是 依赖于 widget 库的 1.x 版本,其中Widgetmeow.

所以现在,我在编译 classpath:

中指定了 widget 的 2 个版本
  1. widgetmakers:widget:1.0.4fizzbuzz 库引入,作为它的依赖;和
  2. widgetmakers:widget:2.0.0 我直接引用

很明显,根据 Widget 哪个版本首先被 class 加载,我们将得到 Widget#meowWidget#bark

Gradle 是否提供任何设施来帮助我?有什么方法可以引入相同 class 的多个版本,并配置 fizzbuzz classes 以使用旧版本的 Widget 和我的 class es使用新版本?如果没有,我能想到的唯一解决方案是:

  1. 我可能能够完成某种着色- and/or 基于 fatjar 的解决方案,也许我将所有依赖项作为包放入 myapp/bin 下,然后给它们不同的版本前缀.诚然,我在这里没有看到明确的解决方案,但我确信有些事情是可行的(但完全 hacky/nasty)。或者...
  2. 仔细检查我的整个依赖关系图,确保我所有的传递依赖关系不相互冲突。在这种情况下,对我来说,这意味着要么向 fizzbuzz 维护者提交拉取请求以将其升级到最新的 widget 版本,要么不幸地降级 myapp 以使用旧的 widget版本。

但是 Gradle(到目前为止)对我来说很神奇。所以我问:这里有什么 Gradle 魔法可以帮到我吗?

Gradle 只会为你的依赖设置 class 路径,它不会提供自己的运行时来封装依赖及其传递依赖。运行时激活的版本将是根据 class 加载规则的版本,我相信这是 class 路径顺序中包含 class 的第一个 jar。 OSGI 提供了可以处理这种情况的运行时,即将推出的模块系统也将如此。

编辑:Bjorn 是正确的,因为它将尝试解决不同版本中的冲突;它将根据其策略编译 classpath,因此您将依赖项放入文件中的顺序并不重要。但是,每个 classname 你仍然只能得到一个 class,它不会解决 OP 的问题

不知道 Gradle 的细节,因为我是 Maven 人员,但无论如何这更通用。你基本上有两个选择(而且都是 hacky):

  1. ClassLoader 魔法。不知何故,您需要说服您的构建系统加载两个版本的库(祝您好运),然后在运行时加载使用旧版本的 类 和具有旧版本的 ClassLoader。我已经这样做了,但这很痛苦。 (像 OSGI 这样的工具可能会减轻一些痛苦)
  2. 包底纹。重新打包使用旧版本库 B 的库 A,使 B 实际上位于 A 内部,但带有特定于 B 的包前缀。这是常见的做法,例如Spring 船 its own version of asm. On the Maven side, the maven-shade-plugin does this, there probably is a Gradle equivalent. Or you can use ProGuard,Jar 操纵的 800 磅重的大猩猩。

如果您有不同版本的库且坐标相同,Gradle冲突解决机制就会发挥作用。

默认解析策略是使用最新请求的库版本。您不会在依赖关系图中获得同一库的多个版本。

如果您在运行时确实需要同一个库的不同版本,您将不得不做一些 ClassLoader 魔术,这绝对是可能的,或者对其中一个库或两者做一些着色。

关于冲突解决,Gradle 内置了默认的最新策略和失败策略,如果依赖图中存在不同版本并且您必须明确解决构建文件中的版本冲突,则会失败.

更糟糕的情况是相同的 class 出现在多个 jar 中。这更加阴险 - 查看来自 Codahale 和 Dropwizard 的指标 jar,这两个 jar 中的相同 class 版本不兼容。 gradle classpath-hell 插件可以检测到这种恐怖。