单个Maven存储库中是否可以使用同一依赖项的多个版本?

67

在Maven仓库中,是否可以声明同一依赖的多个版本

我需要同时使用这些依赖:

    <dependency>
        <groupId>org.bukkit</groupId>
        <artifactId>craftbukkit</artifactId>
        <version>1.7.9-R0.2</version>
        <scope>compile</scope>
    </dependency>
    <dependency>
        <groupId>org.bukkit</groupId>
        <artifactId>craftbukkit</artifactId>
        <version>1.7.2-R0.3</version>
        <scope>compile</scope>
    </dependency>
    <dependency>
        <groupId>org.bukkit</groupId>
        <artifactId>craftbukkit</artifactId>
        <version>1.6.4-R2.0</version>
        <scope>compile</scope>
    </dependency>

因为它们每个都包含我关心的不同软件包:

org.bukkit.craftbukkit.v1_6_R3

org.bukkit.craftbukkit.v1_7_R1

org.bukkit.craftbukkit.v1_7_R3

如果我像第一个片段中所示那样声明依赖项,只有最后一个会生效。在Maven中有什么方法可以实现这一点吗?
@编辑 有什么变通方法吗?

4
为什么需要这样做呢?如果Maven允许这种行为,你将会有一个不可预测的类路径,其中包含多个相同类的副本。Java在运行时不对类进行版本控制,这意味着Maven的限制是一件好事! - Mark O'Connor
2
在开始使用Maven之前,我曾经在类路径中包含了许多相同库的版本,并且没有遇到任何问题。我需要这样做是为了提供对Bukkit的向后兼容性,Bukkit是一个用于运行我的插件的Minecraft游戏服务器。 - wassup
3
抱歉,这对我来说没有意义......如果我支持一个产品的多个版本,我会在我的源代码控制系统中使用不同的分支,并分别构建每个版本。这样每个版本都有自己独特的依赖版本集。重点是在运行时只有一个类的副本。如果您使用相同的jar文件的多个版本,就会遇到一个棘手的问题,即您不知道Java类加载器实际使用的是哪个版本。这就是Maven通过坚持使用一个版本来解决的问题。 - Mark O'Connor
1
你好 Mark - 我可以给你一个需要多个版本的使用案例。在我们公司,我们有一组应用程序正在 Apache Servicemix 上运行。Servicemix 很有趣,因为所有东西都是通过我们称之为功能文件进行配置的,我们调用需要安装的捆绑包。当 servicemix 启动时,它可以直接从 maven 存储库中拉取这些捆绑包 - 这在我们达到认证和生产环境时非常有效,因为我们不允许连接到我们公司的 maven 存储库。 - Scott Conway
1
继续上文。ServiceMix允许我们指向一个目录并将其视为Maven仓库,因此我们使用maven-dependency-plugin将所有依赖项收集到一个目录中,并使用maven-assembly-plugin将其打包成tar文件,作为部署过程的一部分解压缩到我们的ServiceMix中。问题在于,我们的某些功能需要与其他功能(由ServiceMix本身安装的某些功能)不同版本的依赖项,因此我们要么升级/降级所有应用程序到相同的版本,要么手动复制额外的版本到要打包的目录中。 - Scott Conway
显示剩余3条评论
6个回答

40

不会的,Maven 只会解析模块中的一个依赖项,并省略其他版本,以避免任何冲突。即使整个依赖层次结构中使用了多个相同依赖项的版本,Maven 也会使用“在依赖树中最近”的策略选择一个版本。

可以使用不同的profiles指定不同的依赖版本。可以定义并激活每个 Bukkit 版本的配置文件。但是,如果激活了多个配置文件,则仅使用一个版本。

<profiles>
    <profile>
        <id>Bukkit_1_7_9_R02</id>
        <activation>
            ...
        </activation>
        <dependencies>
            <dependency>
                <groupId>org.bukkit</groupId>
                <artifactId>craftbukkit</artifactId>
                <version>1.7.9-R0.2</version>
                <scope>compile</scope>
            </dependency>
        </dependencies>
    </profile>
    <profile>
        <id>Bukkit_1_7_2_R03</id>
        <activation>
            ...
        </activation>
        <dependencies>
            <dependency>
                <groupId>org.bukkit</groupId>
                <artifactId>craftbukkit</artifactId>
                <version>1.7.2-R0.3</version>
                <scope>compile</scope>
            </dependency>
        </dependencies>
    </profile>
    ...
</profiles>

也许可以为特定的软件包/文件集启用配置文件? - wassup
1
有关解决所使用的依赖项的更多信息,请参见此处:依赖机制 - 传递依赖项 - JaXt0r

32

试图欺骗Maven:

<dependency>
    <groupId>org.bukkit</groupId>
    <artifactId>craftbukkit</artifactId>
    <version>1.7.9-R0.2</version>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>org.bukkit.</groupId>
    <artifactId>craftbukkit</artifactId>
    <version>1.7.2-R0.3</version>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>org.bukkit..</groupId>
    <artifactId>craftbukkit</artifactId>
    <version>1.6.4-R2.0</version>
    <scope>compile</scope>
</dependency>

4
@Dmitri,你能解释一下这个作弊的Maven是怎么回事吗?为什么在groupId中加了一个额外的点?最终结果会是什么?应该期望什么? - The-Rohan D. Shah
1
这很有趣,它能够正常工作。我猜这是因为根据 Maven 文档,点号在存储库中被转换为目录分隔符。而且操作系统可以很好地处理文件系统路径中的多个斜杠。例如,Windows 将路径 C:\path\\with\\\slashes.txt 解析为文件 C:\path\with\slashes.txt - Danielku15
1
这是如何工作的?谢谢哈哈! - Anston Sorensen
4
这真的很糟糕。别这么做。有更简洁的解决方案。 - J Fabian Meier
我不明白,为什么那样解决了原帖作者的问题?导入中的包名仍然会冲突,不是吗? - jsalvas

15
这里是Maven文档的相关部分,它解释了当存在多个可能性时,Maven如何选择依赖项的版本。

Dependency mediation - this determines what version of an artifact will be chosen when multiple versions are encountered as dependencies. Maven picks the "nearest definition". That is, it uses the version of the closest dependency to your project in the tree of dependencies. You can always guarantee a version by declaring it explicitly in your project's POM. Note that if two dependency versions are at the same depth in the dependency tree, the first declaration wins. "nearest definition" means that the version used will be the closest one to your project in the tree of dependencies. Consider this tree of dependencies:

A
├── B
│   └── C
│       └── D 2.0
└── E
    └── D 1.0

In text, dependencies for A, B, and C are defined as A -> B -> C -> D 2.0 and A -> E -> D 1.0, then D 1.0 will be used when building A because the path from A to D through E is shorter. You could explicitly add a dependency to D 2.0 in A to force the use of D 2.0, as shown here:

A
├── B
│   └── C
│       └── D 2.0
├── E
│   └── D 1.0
│
└── D 2.0

如果情况是 A-> B -> D-> 1.1.0 和 A-> C-> D-> 2.1.0,会出现什么情况?简而言之,如果不同版本处于相同级别,即存在多个最接近的版本,会发生什么情况? - Deekshith Anand
1
根据Maven文档的说法,“请注意,如果两个依赖版本在依赖树的相同深度上,那么第一个声明将会生效。” - Faisal Feroz

7
不,通常情况下你不能依赖于同一构件的两个版本。但是你可以将它们包含到最终应用程序中。
有时候这种需求是有效的 - 例如,当那个库的维护很差时,他们会重命名一些包并将其作为同一构件的次要版本发布。然后,其他项目将其作为第三方依赖项,并需要不同FQCN下的相同类。
对于这种情况,你可以使用maven-shade-plugin
  • 创建一个带有单个依赖项的maven项目,其中包含你所需的版本之一。
  • 添加shade插件并让它创建一个阴影jar。它基本上会将类重新打包到不同的构件G:A:V中。
  • 对所有所需的版本执行此操作。
  • 你可以使用分类器来区分阴影版本。
  • 在你的项目中,依赖于这些构件。
  • 最后,排除原始依赖项,将它们的范围设置为"provided"。
你可以使用相同的不同变体,最终将这些类放入你的类路径中。例如,使用dependency:copy-dependency插件/目标,在构建期间将该jar安装到你的本地仓库中。或者将类解压缩到${project.build.outputDirectory}(target/classes)中。

0

这是我解决这个问题的方法。顺便说一下,在我的情况下,我正在构建一个RPM。

我使用了maven-dependency-plugin来将被忽略的旧依赖项复制到构建目录中的一个文件夹,然后将该文件复制到暂存区,以便在RPM中包含它。

以下是复制旧依赖项的代码:

  <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-dependency-plugin</artifactId>
    <version>3.1.1</version>
    <executions>
      <execution>
        <id>copy-dependencies</id>
        <phase>prepare-package</phase>
        <goals>
          <goal>copy</goal>
        </goals>
        <configuration>
          <artifactItems>
            <artifactItem>
              <groupId>some.package.group.id</groupId>
              <artifactId>someartifact-id</artifactId>
              <version>1.2.3</version>
              <overWrite>false</overWrite>
              <outputDirectory>${project.build.directory}/older-dependencies</outputDirectory>
            </artifactItem>
          </artifactItems>
        </configuration>
      </execution>
    </executions>
  </plugin>

然后在构建我的RPM过程中,我将这个脚本放入rpm-maven-pluginconfiguration部分。这将把文件复制到RPM的暂存区:

<installScriptlet>
  <script>cp ${project.build.directory}/older-dependencies/* ${project.build.directory}/rpm/${artifactId}/buildroot${installBase}/</script>
</installScriptlet>

0

我还是很新的,但我在使用axis2时遇到的问题是,由于他们对类所做的更改,有些单独的模块需要较早版本,因此顶级依赖项只能捕获其中约一半。其余的我不得不逐个更正pom文件以明确依赖于不同版本。

也许这种方法对您也有用?使插件具有特定依赖项的模块化组件。


如果我理解情况正确的话,我认为你可以在自己的pom文件中的dependencyManagement部分指定它一次。这将强制该版本被拉入你的项目/组件中。 - Vivek Chavda

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