JVM开发人员在这里。最近,我在IRC聊天室甚至在我自己的办公室里看到了所谓的“ 阴影 ” Java库的玩笑。使用的上下文将类似于:
“ 这样为XYZ提供了一个“阴影”客户端。 ”
完美的例子是针对HBase的Jira问题:“ 发布具有阴影依赖项的客户端工件 ”
所以我问:什么是阴影 JAR,“阴影”是什么意思?
JVM开发人员在这里。最近,我在IRC聊天室甚至在我自己的办公室里看到了所谓的“ 阴影 ” Java库的玩笑。使用的上下文将类似于:
“ 这样为XYZ提供了一个“阴影”客户端。 ”
完美的例子是针对HBase的Jira问题:“ 发布具有阴影依赖项的客户端工件 ”
所以我问:什么是阴影 JAR,“阴影”是什么意思?
Answers:
着色依赖项是包含和重命名依赖项(从而重新定位类并重写受影响的字节码和资源)以创建与您自己的代码捆绑在一起的私有副本的过程。
由于maven shade plugin,该术语存在一些混淆,在该单一名称下,它做了两件事(引用自己的页面):
该插件提供了将工件打包(包括其依赖项)并遮蔽(即重命名)某些依赖项的包的功能。
因此,阴影部分实际上是可选的:插件允许在jar(胖jar)中包含依赖项,并可以选择重命名(阴影)依赖项。
添加另一个来源:
要遮蔽库,就是要获取该库的内容文件,将它们放在您自己的jar中,然后更改其包。这与打包不同,打包只是将库文件放在您自己的jar中,而无需将它们重新定位到其他包中。
从技术上讲,依赖项已被阴影化。但是通常将带有阴影的胖子依赖项称为“阴影罐”,如果该jar是另一个系统的客户端,则可以将其称为“阴影客户端”。
这是您在问题中链接的HBase的Jira问题的标题:
发布具有阴影依赖项的客户端工件
因此,在这篇文章中,我试图不将它们混为一谈地介绍这两个概念。
Uber-jars通常用于将应用程序作为单个文件发布(使其易于部署和运行)。它们也可以用来与它们的依赖的部分(或全部)沿着船库阴影,以便被其他应用程序(可能使用不同版本的库)使用时避免冲突。
有多种构建uber-jar的方法,但maven-shade-plugin
通过其类重定位功能又走了一步:
如果将uber JAR用作其他项目的依赖项,则由于uber JAR中工件的依赖项中直接包含类,因此会由于类路径上的重复类而导致类加载冲突。为了解决这个问题,可以重新定位阴影工件中包含的类,以创建其字节码的私有副本。
(历史记录:Jar Jar Links之前提供了该重定位功能)
因此,除非您在API中公开了那些库中的类,否则您可以使您的库依赖项成为实现细节。
比方说,我有一个项目,ACME Quantanizer™,提供DecayingSyncQuantanizer
一流的,并且依赖于Apache的公地RNG(因为当然是正确quantanize你需要XorShift1024Star
,杜)。
如果我使用shade maven插件生成一个uber-jar,并且向内看,我会看到以下类文件:
com/acme/DecayingSyncQuantanizer.class
org/apache/commons/rng/RandomProviderState.class
org/apache/commons/rng/RestorableUniformRandomProvider.class
...
org/apache/commons/rng/core/source64/XorShift1024Star.class
org/apache/commons/rng/core/util/NumberFactory.class
现在,如果我使用类重定位功能:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<relocations>
<relocation>
<pattern>org.apache.commons</pattern>
<shadedPattern>com.acme.shaded.apachecommons</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>
uber-jar的内容如下所示:
com/acme/DecayingSyncQuantanizer.class
com/acme/shaded/apachecommons/rng/RandomProviderState.class
com/acme/shaded/apachecommons/rng/RestorableUniformRandomProvider.class
...
com/acme/shaded/apachecommons/rng/core/source64/XorShift1024Star.class
com/acme/shaded/apachecommons/rng/core/util/NumberFactory.class
这不仅是重命名文件,它还会重写引用重定位类的字节码(因此,我自己的类和commons-rng类都已转换)。
此外,Shade插件还将生成一个新的POM(dependency-reduced-pom.xml
),其中从该<dependencies>
部分中删除了阴影依赖项。这有助于将着色的jar用作另一个项目的依赖项。因此,您可以发布该jar而不是基础jar 或同时发布两者(对带阴影的jar使用限定符)。
所以这可能非常有用...
...但是这也带来了许多问题。将所有依赖项聚合到jar内的单个“命名空间”中可能会很混乱,并且需要对资源进行着色和处理。
例如:如何处理包含类名或包名的资源文件?资源文件(例如服务提供商描述符)都位于其中META-INF/services
?
shade插件提供了资源转换器,可以帮助您:
只要没有重叠,将来自多个工件的类/资源聚合到一个超级JAR中就很简单。否则,需要某种逻辑来合并来自多个JAR的资源。这就是资源转换器的作用。
但这仍然很混乱,问题几乎是无法预料的(很多时候,您很难在生产中发现问题)。看看为什么我们停止建造胖子。
总而言之,将胖子jar部署为独立的应用程序/服务仍然很常见,您只需要了解这些陷阱,对于某些陷阱,可能需要阴影或其他技巧。
还有许多困难的问题(调试,可测试性,与OSGi和奇异类加载器的兼容性...)。
但更重要的是,当您生成一个库时,您认为可以控制的各种问题现在变得越来越复杂,因为您的jar将在许多不同的上下文中使用(不同于您将胖jar部署为独立的应用程序/服务在受控环境中)。
例如,ElasticSearch曾经在它们运送的罐子中添加一些依赖项,但是他们决定停止这样做:
在2.0版之前,Elasticsearch是作为JAR提供的,其中一些(但不是全部)常见的依赖项被阴影化并打包在同一工件中。这帮助将Elasticsearch嵌入自己的应用程序中的Java用户避免了诸如Guava,Joda,Jackson等模块的版本冲突。当然,仍然存在诸如Lucene之类的其他无阴影依赖项列表,这些依赖项仍然可能导致冲突。
不幸的是,着色是一个复杂且容易出错的过程,它可以解决某些人的问题,同时又为其他人带来问题。阴影使开发人员和插件作者很难正确编写和调试代码,因为在构建过程中会重命名软件包。最终,我们曾经在没有阴影的情况下测试Elasticsearch,然后交付了有阴影的罐子,而且我们不希望交付任何我们未测试的东西。
我们决定发布Elasticsearch,而从2.0开始不加阴影。
请注意,它们也指的是阴影依赖项,而不是阴影jar
让我在实际上负责创建阴影罐子的软件的帮助下(至少在使用maven时)回答问题。
取自Apache Maven Shade插件主页:
该插件提供了将工件打包(包括其依赖项)并遮蔽(即重命名)某些依赖项的包的功能。
默认情况下,带阴影的jar或uber-jar aka fat jar将包含运行Java应用程序所需的每个依赖关系,因此在类路径中不需要其他依赖关系。您只需要正确的Java版本即可运行您的应用程序。带阴影的jar将有助于避免部署/类路径问题,但是它将比原始应用程序jar大得多,并且无法帮助您避免jar地狱。