maven-shade-plugin 的用途是啥,为啥要重新定位 Java 包?

Posted

技术标签:

【中文标题】maven-shade-plugin 的用途是啥,为啥要重新定位 Java 包?【英文标题】:What is the maven-shade-plugin used for, and why would you want to relocate Java packages?maven-shade-plugin 的用途是什么,为什么要重新定位 Java 包? 【发布时间】:2012-11-17 04:50:41 【问题描述】:

我发现有人在 pom.xml 中使用了 maven-shade-plugin。我以前从未使用过 maven-shade-plugin(我是 Maven n00b),所以我试图了解使用它的原因以及它的作用。

我看了Maven docs,但是我看不懂这个说法:

这个插件提供了将工件打包到 uber-jar 中的能力,包括它的依赖项和遮蔽 - 即重命名 - 一些依赖项的包。

页面上的文档似乎对新手不太友好。

什么是“超级罐”?为什么有人想做一个?重命名依赖项的包有什么意义?我试图浏览 maven-shade-plugin apache 页面上的示例,例如“为 Uber Jar 选择内容”,但我仍然无法理解“着色”的作用。

任何指向说明性示例/用例的指针(解释为什么在这种情况下需要阴影 - 它解决了什么问题)将不胜感激。最后,什么时候应该使用 maven-shade-plugin?

【问题讨论】:

对于命名“uber jar”,我与德语有唯一的关联,其中“über”的意思是“结束”。从字面上看,它的意思是“jar-over-all-other-jars”。 “阴影”与碰撞的类或资源所需的“包重新分配”相同。 s/reallocation/relocation/ 在上面的评论中。 超级罐子就像一个戒指:一罐子统治他们,一罐子找到他们,一罐子把他们都带过来,在黑暗中束缚他们。 uber 是一个非常具有误导性的命名(与“Uber Technologies,Inc”冲突) 现在我们就叫它胖罐 【参考方案1】:

Uber JAR,简而言之,就是一个包含一切的 JAR。

通常在 Maven 中,我们依赖依赖管理。工件仅包含其自身的类/资源。 Maven 将负责根据项目的构建时间找出项目的所有工件(JAR 等)。

一个 uber-jar 是一个包含所有依赖项的东西,并提取依赖项的内容并将它们与项目本身的类/资源放在一个大 JAR 中。通过拥有这样一个 uber-jar,执行起来很容易,因为您只需要一个大 JAR 而不是大量的小 JAR 来运行您的应用程序。在某些情况下,它还可以简化分发。

附带说明:避免使用 uber-jar 作为 Maven 依赖项,因为它会破坏 Maven 的依赖项解析功能。通常我们只为最终工件创建一个 uber-jar 用于实际部署或手动分发,而不是用于放入 Maven 存储库。


更新:我刚刚发现我还没有回答问题的一部分:“重命名依赖项的包有什么意义?”。以下是一些简短的更新,希望能帮助有类似问题的人。

创建一个易于部署的 uber-jar 是 shade 插件的一个用例。还有其他涉及包重命名的常见用例。

例如,我正在开发Foo 库,它依赖于Bar 库的特定版本(例如1.0)。假设我无法使用其他版本的 Bar lib(因为 API 更改或其他技术问题等)。如果我在 Maven 中简单地将Bar:1.0 声明为Foo 的依赖项,则可能会遇到问题:Qux 项目依赖于Foo,还有Bar:2.0(它不能使用@987654329 @ 因为Qux 需要使用Bar:2.0 中的新功能)。这是两难的选择:Qux 应该使用Bar:1.0Qux 的代码不起作用)还是Bar:2.0Foo 的代码不起作用)?

为了解决这个问题,Foo的开发者可以选择使用shade插件重命名Bar的用法,使Bar:1.0jar中的所有类都嵌入到Foojar中,包嵌入的Bar 类从com.bar 更改为com.foo.bar。通过这样做,Qux 可以安全地依赖于Bar:2.0,因为现在Foo 不再依赖于Bar,它正在使用位于另一个包中的“已更改”Bar 的自己的副本。

【讨论】:

谢谢,这很有帮助。是不是因为隐藏了 uber-jar 里面的依赖和其他 jar 就叫“shading”? 我不太确定名称背后的原因,但从它的首页看来,“阴影”似乎是在描述依赖关系的“隐藏”。由于您可以选择在 shaded JAR 中包含一些依赖项,因此 shade 插件还将为您生成正确的 POM,从而删除包含的依赖项。似乎阴影正在描述这个过程 @AdrianShum :你能建议如何在不使用阴影插件的情况下创建一个 uber jar 吗?或者具体如何解决“破坏Maven的依赖解析功能”? @SurajMenon :使用 Shade 插件是创建 uber-jar 的最简单方法。但是,您也可以使用 Assembly 插件,并使用内置的 jar-with-dependency 描述符。对于使用 uber-jar 依赖 roslution 的问题,我在回答中已经提到:不要使用 uber-jar 作为依赖,句号。更详细一点:在创建 uber-jar 之前,您应该有一个具有正常依赖关系的正常项目。该原始工件是您应该用作依赖项的工件(而不是 uber-jar) 只是一个小问题:它会阻止Class.forName 工作吗?【参考方案2】:

我认为需要“阴影”jar 的一个例子是 AWS Lambda 函数。它们似乎只允许您上传 1 个 jar,而不是像您在典型的 .war 文件中找到的整个 .jar 集合。因此,创建一个包含项目所有依赖项的单个 .jar 即可完成此操作。

【讨论】:

使用 GraalVM 创建 AWS Lambda 现在支持的本机映像。低毫秒的启动时间:)【参考方案3】:

小警告

虽然这没有描述为什么要使用 maven-shade-plugin(因为所选答案描述得很好),但我想指出我遇到了问题。它改变了 JAR(因为它正在做的事情)并导致我的软件回归。

所以,我没有使用这个(或 maven-jarjar-plugin),而是使用了 JarJar 的二进制文件,它似乎可以正常工作。

我在这里发布我的解决方案,因为我花了一些时间才找到一个合适的解决方案。


下载 JarJar 的 JAR 文件

您可以从这里下载 jar: https://code.google.com/p/jarjar/ 在左侧菜单中,您有一个下载链接。


如何使用 JarJar 将 JAR 的类从一个包重新定位到另一个包

在本例中,我们将把包从“com.fasterxml.jackson”更改为“io.kuku.dependencies.com.fasterxml.jackson”。 - 源 JAR 称为“jackson-databind-2.6.4.jar”,新修改的(目标)JAR 称为“kuku-jackson-databind-2.6.4.jar”。 - “jarjar” JAR 文件是 1.4 版

    创建一个“rules.txt”文件。文件的内容应该是(注意“@”字符之前的句点): 规则 com.fasterxml.jackson.** io.kuku.dependencies.com.fasterxml.jackson.@1

    运行以下命令: java -jar jarjar-1.4.jar process rules.txt jackson-databind-2.6.4.jar kuku-jackson-databind-2.6.4.jar


将修改后的 JAR 安装到本地存储库

在这种情况下,我正在安装位于“c:\my-jars\”文件夹中的 3 个文件。

mvn install:install-file -Dfile=C:\my-jars\kuku-jackson-annotations-2.6.4.jar -DgroupId=io.kuku.dependencies -DartifactId=kuku-jackson-annotations -Dversion=2.6 .4 -Dpackaging=jar

mvn install:install-file -Dfile=C:\my-jars\kuku-jackson-core-2.6.4.jar -DgroupId=io.kuku.dependencies -DartifactId=kuku-jackson-core -Dversion=2.6 .4 -Dpackaging=jar

mvn install:install-file -Dfile=C:\my-jars\kuku-jackson-databind-2.6.4.jar -DgroupId=io.kuku.dependencies -DartifactId=kuku-jackson-annotations -Dversion=2.6 .4 -Dpackaging=jar


在项目的 pom 中使用修改后的 JAR

在本例中,这是项目 pom 中的“依赖项”元素:

<dependencies>
    <!-- ================================================== -->
    <!-- kuku JARs -->
    <!-- ================================================== -->
    <dependency>
        <groupId>io.kuku.dependencies</groupId>
        <artifactId>kuku-jackson-annotations</artifactId>
        <version>2.6.4</version>
    </dependency>
    <dependency>
        <groupId>io.kuku.dependencies</groupId>
        <artifactId>kuku-jackson-core</artifactId>
        <version>2.6.4</version>
    </dependency>
    <dependency>
        <groupId>io.kuku.dependencies</groupId>
        <artifactId>kuku-jackson-databind</artifactId>
        <version>2.6.4</version>
    </dependency>
</dependencies>

【讨论】:

感谢您提供此替代建议。这不是我想要的,但结果证明这是一种更简单、更快捷的解决方案,可以在永远不会改变的遗留库上应用 1-time 包翻译。 你有没有通过比较它们各自输出的内容找出这些失败的原因?【参考方案4】:

我最近想知道为什么 elasticsearch 会隐藏并重新定位它的一些(但不是全部)依赖项。以下是项目维护者 @kimchy 的解释:

阴影部分是有意的,我们在elasticsearch中使用的阴影库是elasticsearch的所有意图和目的部分,使用的版本与elasticsearch公开的内容以及它如何使用库基于库的内部结构密切相关工作(以及版本之间的变化),netty 和 guava 就是很好的例子。

顺便说一句,我实际上提供了几个弹性搜索罐子,一个没有 lucene 阴影,一个带有 Lucene 阴影。不知道如何用 maven 来做。例如,我不想提供一个不遮蔽 netty/jackson 的版本,因为 elasticsearch 对它们的使用很深(例如,将即将到来的缓冲改进与除当前版本之外的任何先前版本的 netty 一起使用将与使用更少的内存相比,实际上使用更多的内存)。

-- https://github.com/elasticsearch/elasticsearch/issues/2091#issuecomment-7156766

另一个来自 drewr

阴影对于保持我们的依赖项(特别是 netty、lucene、guava)接近我们的代码很重要,这样即使上游提供者落后,我们也可以解决问题。我们可能会分发代码的模块化版本,这将有助于解决您的特定问题(例如#2091),但此时我们不能简单地删除阴影依赖项。您可以根据自己的目的构建本地版本的 ES,直到有更好的解决方案。

-- https://github.com/elasticsearch/elasticsearch/pull/3244#issuecomment-20125452

所以,这是一个用例。作为一个说明性示例,下面是 maven-shade-plugin 在 elasticsearch 的 pom.xml (v0.90.5) 中的使用方式。 artifactSet::include 行指示它将哪些依赖项拉入 uber JAR(基本上,当目标 elasticsearch jar 生成时,它们会被解压缩并与 elasticsearch 自己的类一起重新打包。(如果您还不知道这一点,请JAR 文件只是一个 ZIP 文件,其中包含程序的类、资源等以及一些元数据。您可以提取一个以查看它是如何组合在一起的。)

relocations::relocation 行是相似的,除了在每种情况下它们还将指定的替换应用到依赖项的类 - 在这种情况下,将它们置于 org.elasticsearch.common 之下。

最后,filters 部分从目标 JAR 中排除了一些不应该存在的东西 - 例如 JAR 元数据、ant 构建文件、文本文件等,它们与一些依赖项打包在一起,但没有属于 uber JAR。

<plugins>
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>2.1</version>
        <executions>
            <execution>
                <phase>package</phase>
                <goals>
                    <goal>shade</goal>
                </goals>
            </execution>
        </executions>
        <configuration>
            <minimizeJar>true</minimizeJar>
            <artifactSet>
                <includes>
                    <include>com.google.guava:guava</include>
                    <include>net.sf.trove4j:trove4j</include>
                    <include>org.mvel:mvel2</include>
                    <include>com.fasterxml.jackson.core:jackson-core</include>
                    <include>com.fasterxml.jackson.dataformat:jackson-dataformat-smile</include>
                    <include>com.fasterxml.jackson.dataformat:jackson-dataformat-yaml</include>
                    <include>joda-time:joda-time</include>
                    <include>io.netty:netty</include>
                    <include>com.ning:compress-lzf</include>
                </includes>
            </artifactSet>
            <relocations>
                <relocation>
                    <pattern>com.google.common</pattern>
                    <shadedPattern>org.elasticsearch.common</shadedPattern>
                </relocation>
                <relocation>
                    <pattern>gnu.trove</pattern>
                    <shadedPattern>org.elasticsearch.common.trove</shadedPattern>
                </relocation>
                <relocation>
                    <pattern>jsr166y</pattern>
                    <shadedPattern>org.elasticsearch.common.util.concurrent.jsr166y</shadedPattern>
                </relocation>
                <relocation>
                    <pattern>jsr166e</pattern>
                    <shadedPattern>org.elasticsearch.common.util.concurrent.jsr166e</shadedPattern>
                </relocation>
                <relocation>
                    <pattern>org.mvel2</pattern>
                    <shadedPattern>org.elasticsearch.common.mvel2</shadedPattern>
                </relocation>
                <relocation>
                    <pattern>com.fasterxml.jackson</pattern>
                    <shadedPattern>org.elasticsearch.common.jackson</shadedPattern>
                </relocation>
                <relocation>
                    <pattern>org.joda</pattern>
                    <shadedPattern>org.elasticsearch.common.joda</shadedPattern>
                </relocation>
                <relocation>
                    <pattern>org.jboss.netty</pattern>
                    <shadedPattern>org.elasticsearch.common.netty</shadedPattern>
                </relocation>
                <relocation>
                    <pattern>com.ning.compress</pattern>
                    <shadedPattern>org.elasticsearch.common.compress</shadedPattern>
                </relocation>
            </relocations>
            <filters>
                <filter>
                    <artifact>*:*</artifact>
                    <excludes>
                        <exclude>META-INF/license/**</exclude>
                        <exclude>META-INF/*</exclude>
                        <exclude>META-INF/maven/**</exclude>
                        <exclude>LICENSE</exclude>
                        <exclude>NOTICE</exclude>
                        <exclude>/*.txt</exclude>
                        <exclude>build.properties</exclude>
                    </excludes>
                </filter>
            </filters>
        </configuration>
    </plugin>
</plugins>

【讨论】:

以上是关于maven-shade-plugin 的用途是啥,为啥要重新定位 Java 包?的主要内容,如果未能解决你的问题,请参考以下文章

Netbeans:项目主神器通过maven-shade-plugin处理

如何判断哪个签名的 jar 导致 maven-shade-plugin 失败?

Javassist。主要思想是啥,真正的用途是啥?

jenkins 用户文件夹的用途是啥,这些配置文件是啥?

maven-shade-plugin 和自定义包装类型

SCSI LUN id 是啥意思,它的用途是啥?