如何使用Gradle解决依赖冲突?

6
我正在使用Dropwizard和Titan DB开发一个项目。两者都依赖于Google Guava,但其中一个依赖于版本15,另一个依赖于版本18。在运行时会出现以下错误:
! java.lang.IllegalAccessError: tried to access method com.google.common.base.Stopwatch.<init>()V from class com.thinkaurelius.titan.graphdb.database.idassigner.StandardIDPool$ID
BlockRunnable

我研究了这个错误并发现,它是由Titan的Guava 15.0依赖被Guava 18.0替换导致的。
我对Java和Gradle不太了解。我正在使用Gradle的javaapplication插件来构建和运行主类,使用gradle run命令。我该如何解决这个问题?
这是我的build.gradle文件:
apply plugin: 'java'
apply plugin: 'application'

mainClassName = "com.example.rest.App"

repositories {
    mavenCentral()
}

dependencies {
    compile (
        [group: 'io.dropwizard', name: 'dropwizard-core', version: '0.8.0-rc1'],
        [group: 'com.thinkaurelius.titan', name: 'titan-core', version: '0.5.1'],
        [group: 'com.thinkaurelius.titan', name: 'titan-berkeleyje', version: '0.5.1'],
        [group: 'com.tinkerpop', name: 'frames', version: '2.6.0']
    )
    testCompile group: 'junit', name: 'junit', version: '3.8.1'
}

run {  
    if ( project.hasProperty("appArgs") ) {  
        args Eval.me(appArgs)  
    }  
}

如果可能的话,我宁愿不为运行/测试构建fat jars。我目前正在阅读http://www.gradle.org/docs/current/userguide/dependency_management.html。 - Dmitry Minkovsky
你能展示一下你的 build.gradle 文件吗? - fge
@fge 刚刚发布了。我越读 dependency_management.html,就越感觉需要继续阅读那个页面。 - Dmitry Minkovsky
迄今为止,我认为这是我找到的处理此问题的最佳描述:https://www.elastic.co/blog/to-shade-or-not-to-shade - Dmitry Minkovsky
1个回答

9

默认情况下,当存在版本冲突时,Gradle 会选择最高版本的依赖。您可以使用自定义的 resolutionStrategy 强制使用特定版本(从 http://www.gradle.org/docs/current/dsl/org.gradle.api.artifacts.ResolutionStrategy.html 改编):

configurations.all {
  resolutionStrategy {
    force 'com.google.guava:guava:15.0'
  }
}

这不会添加对guava 15.0的依赖,但是如果存在任何依赖关系(即使是传递性的),则强制使用15.0。您可以使用“gradle dependencies”和“gradle dependencyInsight…”获取有关依赖项来源的更多信息。FYI,您似乎请求了几个不同版本的Guava(11.0.2、14.0.1、15.0和18.0)。希望这能帮到您。

谢谢大佬。我从Node.js转到Java,其中npm和运行时路径解析机制使管理传递属性变得轻松。我是否可以在Java中模拟这种东西?那叫做shading吗? - Dmitry Minkovsky
是的,阴影转换所有依赖项的类名,以便它们不会冲突(但您可以有许多重复的类)。OSGi 有类似的目标(运行时解析依赖关系和分离的类加载器)。 - bigguy
谢谢,我也会研究一下OSGi。你所关心的主要问题是应用程序重复的内存占用吗?我认为与确切声明的依赖版本运行通常比RAM使用更重要。 - Dmitry Minkovsky
旧版JVM曾经为已加载的类(PermGen)分配单独的内存空间,因此您可能会耗尽内存。自JDK8以来,这已经不存在了。如果您在外部接口中依赖于第三方类,则无法使用Shading。这将迫使您API的用户使用与您相同的版本。 - bigguy
谢谢大佬。我还没有了解过着色,所以当我了解后,也许问题就会显而易见了。但是为什么着色会给我的库引入这个问题呢?客户端着色是否也可以解决这个问题? - Dmitry Minkovsky
Shading是一种后处理步骤,它更改类文件的字节码以重命名类并创建字节码的私有副本。因此,如果您在外部接口中使用了com.thirdparty.Foo,将其遮蔽为my.copy.com.thirdparty.Foo,客户端需要使用my.copy.com.thirdparty.Foo类而不是com.thirdparty.Foo。这就是为什么遮蔽通常只限于内部依赖关系的原因。 - bigguy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接