简介
该插件提供了将工件打包成 Uber jar 的功能,包括其依赖项,并对某些依赖项的包进行处理。Shade 插件只有一个目标:shade:shade
绑定到 package
阶段,用于创建 Uber jar。问题来了,Uber jar 是什么 jar?请看如下介绍。
Jar 类型
在 Java 中,如果要运行 Java 程序,需要一个包含 main 方法类的 jar 包或类文件,然后执行命令:
# 运行一个含有main方法的.class文件
java $class
# 运行一个jar包,实际上运行的是该jar包中某个类的main方法,这需要在
# 该jar包的清单文件(manifest)中指定一个有main方法的类
java -jar $jarfile
# 使用 -cp (-classpath) 选项用于将jar包添加到类路径中,同时运行jar包
# 里清单文件中所指定的含有main方法的特定类
java -cp (-classpath) $path (directory/jar file/zip file) # zip 文件应符合jar格式规范
# 如果在jar包里清单文件(manifest)中未指定含有main方法的类,或
# 者该jar包中存在多个含有main方法的类,则可以在上面的命令后面添加指
# 定类的全限定名称(如com.my.Test)来运行该类。
# 这相当于在上面第一条命令中通过 -cp 选项添加了运行的依赖
java -cp (-classpath) $path (directory/jar file/zip file) com.my.Test # zip 文件应符合jar格式规范
关于如何在清单文件中指定默认运行的类以及classpath的内容,请参考 jar 工具详解 中关于
META-INF/MANIFEST.MF
的相关介绍。
这种方法会有一些缺点,因为大多数 Java 程序都包含很多依赖项。如果要启动此程序,必须传递 classpath
以指定这些依赖的包文件,并且必须在服务器上指定类路径,这不是很方便,特别是随着 DevOps/Microservices 的普及,这种指定 classpath
的方法过于死板。我们可以直接构建聚合的 jar 包并发布或运行它。
Executable Jar
可执行 jar 包通常意味着所有依赖的 jar 包都放在一个大的入口 jar 包中。这个入口 jar 包中包含运行时需要依赖的所有 jar 包和类文件。您可以将依赖的 jar 包直接放在入口 jar 包中,如下所示:
├─executable jar
│ ├─META-INF
│ │ ├─MANIFEST.MF
│ ├─com...
│ ├─lib
│ │ ├─io.netty....jar
│ │ ├─com.google....jar
│ │ ├─com.github....jar
│ │ ├─org.apache.....jar
您还可以将依赖的 jar 包中的文件复制到入口 jar 包中,如下所示:
├─executable jar
│ ├─META-INF
│ │ ├─MANIFEST.MF
│ ├─com...
│ ├─io.netty.... classes
│ ├─com.google.. classes
│ ├─com.github.. classes
│ ├─org.apache.. classes
Spring Boot 打包的就是可执行 jar 。Spring Boot 在 spring-boot-maven-plugin
插件可以在构建过程中将所有依赖的 jar 包打包成一个入口 jar 文件,并通过 Spring Boot 的类加载器和启动类来将这些依赖 jar 包加载到可执行的入口 jar 包中,上面描述的第一种方法是:将依赖的 jar 包直接放进入口 jar 包中。
Uber Jar
当我第一次看到这个词时,我不知所措。我不知道这个词是什么意思。优步出租车?查找信息后,我发现 Uber jar 的原始单词是 Über jar
,是一个德语单词,可以解释为 over 或 end,但在实际上下文中,将其翻译为 everything 可能更合适。
这个术语最初是由开发人员创造的,他们认为将所有依赖项和自己的代码放入 jar 文件可以解决许多冲突。但大多数输入法很难输入 Ü
,所以被称为 Uber。
Fat jar
Fat jar 是对 fat 和 big 的一个很好的解释:Fat jar 和 Uber jar 表示包含所有依赖包的 jar 包。
Shade jar/Shadow jar
Shade jar 就是将依赖包也打包到入口 jar 包的 jar 包,并提供对某些依赖包进行重命名的功能。例如,一个 Maven 项目依赖于许多第三方软件包,但您希望在实际打包期间重命名一些软件包。重命名的过程在这里可以称为 Shade(着色)。
为什么要重命名依赖包呢?例如,当我们开发时还需要依赖一些第三方软件包,比如 netty
,所以我们需要在实际操作中以 –javaagent
或动态附加 jar 包的形式加载我们的代理 jar 包。这里加载的代理只能是一个独立的 jar 包,因此首先,我们需要通过在 jar 包中键入我们的代理及其依赖包来构建一个 Uber jar。然后,我们需要考虑类包冲突的问题,因为代理中的依赖包类和目标 JVM 进程中的类可能会发生冲突,例如,代理依赖于 netty 4.1.58.final
,而目标 JVM 进程依赖于 netty 4.0.14.final
,我们的代理使用 4.0.14 中不存在的 API。此时,程序将产生找不到方法的异常,因为目标进程已加载该类,并且不会重复加载代理程序包中具有相同全限定名的类。
在构建 Uber jar 时,可以修改并重新定位依赖包的包名,这比下载项目的源代码重构包名再打包要方便的多。比如,将 io.netty
修改为 com.github.kongwu.io.netty
,同时,Java 代码中的所有引用在重新定位后都使用被修改后的包名。这样,通过修改包名,完全避免了依赖性包类冲突的问题。Google 也开源了一个类似功能的 jar 文件,叫做 jarjar.jar
(好多 jar 啊)。
上述 relocation 行为称为 Shade 或 Shadow。Maven 中的 Shade 插件可以将程序打包到单独的jar包中,包括依赖项包。另一个类似的 Maven Assembly 插件也可以达到同样的效果。Gradle 中也有类似的插件,功能也很强大,也支持 Shade 功能。
所有可选参数
-
<artifactSet>
。声明要从最终工件中包括/排除的工件。工件由组合标识符表示,其一般形式为groupId:artifactId:type:classifier
。从 1.3 版开始,通配符*
和?
可以在这些复合标识符的子部分中使用,以进行模式匹配。为方便起见,语法groupId
等效于groupId:*:*:*
,groupId:artifactId
等效于groupId:artifactId:*:*
,groupId:artifactId:classifier
等效于groupId:artifactId:*:classifier
。例如:<artifactSet> <includes> <include>org.apache.maven:*</include> </includes> <excludes> <exclude>*:maven-core</exclude> </excludes> </artifactSet>
<createDependencyReducedPom>
。表示是否为 Shaded 工件生成简化的 POM。如果设置为true
(默认),则已包含在 Uber jar 中的依赖项将从生成的POM 的<dependencies>
部分中删除。简化后的 POM 将命名为dependency-reduced-pom.xml
,并存储在与 Shaded 工件相同的目录中。除非还指定dependencyReducedPomLocation
,否则插件将在项目basedir
中创建一个名为dependency-reduced-pom.xml
的临时文件。<createSourcesJar>
。如果为true
,它也将尝试创建一个 sources jar。<createTestSourcesJar>
。如果为true
,它将尝试创建一个 test sources jar。<dependencyReducedPomLocation>
。在何处生成dependency-reduced-pom.xml
文件。默认为${basedir}/dependency-reduced-pom.xml
。注意:使用${basedir}
以外的目录来作为该参数的值将更改后续 Shade 操作中所涉及的${basedir}
的值。这通常不是你想要的。这被认为是这个插件的一个公开问题。-
<filters>
。要使用的归档筛选器。允许以复合标识符的形式指定一个工件,该标识符由artifactSet
和一组include/exclude
文件模式使用,用于过滤将哪些内容添加到 Shade jar 中。从逻辑角度看,include 在 exclude 之前进行处理,因此可以使用 include 从归档中收集一组文件,然后使用 exclude 进一步减少该组文件。默认情况下,包含所有文件,不排除任何文件。如果对一个工件应用多个过滤器,那么匹配文件的交集将包含在最终的 jar 中。例如:<filters> <filter> <artifact>junit:junit</artifact> <includes> <include>org/junit/**</include> </includes> <excludes> <exclude>org/junit/experimental/**</exclude> </excludes> </filter> </filters>
<finalName>
。要 Shade 的artifactId
的名称。如果您想更改该工件的名称,可以使用<build><finalName>
设置。如果将其设置为与<build><finalName>
不同的内容,则不会执行文件替换,即使正在使用shadedArtifactAttached
。<generateUniqueDependencyReducedPom>
。在${basedir}/drp-UNIQUE.pom
中创建一个 dependency-reduced 的 POM,默认为false
。这避免了并行构建的构建冲突,而无需将 dependency-reduced 的 POM 移动到其他目录。名为maven.shade.dependency-reduced-pom
property 用于设置为生成的文件名。<keepDependenciesWithProvidedScope>
。如果为true
,则依赖项保留在 pom 中,但其scope
为provided
;如果为false
,则删除依赖项。<minimizeJar>
。如果为true
,则依赖关系将在类级别上剥离,仅为工件所需的可传递外壳。注意:使用此功能需要 Java 1.5 或更高版本。<outputDirectory>
。Shaded 工件的输出目录。<outputFile>
。Shaded 工件的输出文件路径。设置此参数后,创建的归档文件既不会替换项目的主工件,也不会附加它。因此,此参数会导致使用时忽略参数finalName
、shadedArtifactAttached
、shadedClassifierName
和createDependencyReducedPom
。<promoteTransitiveDependencies>
。如果为true
,则已删除的依赖项的可传递依赖将升级为直接依赖项。这应该允许使用新的 Shade jar 来替换已移除的依赖,并且一切仍然可以工作。-
<relocations>
。要重新定位的包。例如:<relocations> <relocation> <pattern>org.apache</pattern> <shadedPattern>hidden.org.apache</shadedPattern> <includes> <include>org.apache.maven.*</include> </includes> <excludes> <exclude>org.apache.maven.Public*</exclude> </excludes> </relocation> </relocations>
仅从版本 1.4 起才存在对 include 的支持。
<shadeSourcesContent>
。如果为true
,它将在创建 sources jar 时尝试着色 java 源文件的内容。如果为false
,它只会将 java 源文件重新定位到着色路径,但不会修改 java 源文件的实际内容。默认为false
。<shadeTestJar>
。如果为true
,则也会创建一个着色的 test jar 工件。默认为false
。<shadedArtifactAttached>
。定义着色工件是否应作为classifier
附加到原始工件。如果为false
,着色的 jar 将是项目的主要工件。<shadedArtifactId>
。着色工件的名称,默认为${project.artifactId}
。当想使用一个不同的artifaceId
并保留原始的artifaceId
值时可以使用该参数。如果原始的artifactId
是foo
,那么最终的工件将类似于 foo-1.0.jar。因此,如果您更改了该参数值,您可能创建类似 foo-special-1.0.jar 名称的文件。<shadedClassifierName>
。附加着色工件时使用的classifier
的名称。默认为shaded
。<shadedGroupFilter>
。如果指定,这将只包括具有以此开头的groupId
的工件。<shaderHint>
。您可以在这里传递有关您自己的着色器实现 plexus 组件的roleHint
。<transformers>
。要使用的资源转换器。<useBaseVersion>
。如果为true
,则简化 pom 中的每个依赖项的版本将基于原始依赖项的baseVersion
,而不是其解析的版本。例如,如果原始 pom(可传递)依赖于a:a:2.7-SNAPSHOT
,如果useBaseVersion
设置为 false(默认),则简化 pom 将依赖于a:a:2.7-20130312.222222-12
,而如果useBaseVersion
设置为true
,则简化 pom 将依赖于a:a:2.7-SNAPSHOT
。
示例
选择 Uber Jar 中要包含的内容
下面的 POM 代码片段显示了如何控制 Uber jar 中应包括/排除哪些项目依赖项:
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<artifactSet>
<excludes>
<exclude>classworlds:classworlds</exclude>
<exclude>junit:junit</exclude>
<exclude>jmock:*</exclude>
<exclude>*:xml-apis</exclude>
<exclude>org.apache.maven:lib:tests</exclude>
<exclude>log4j:log4j:jar:</exclude>
</excludes>
</artifactSet>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
当然,也可以使用 <includes>
用于指定打包时包含哪些工件。模式中工件由组合标识符表示,其形式为 groupId:artifactId[[:type]:classifier]
。从版本 1.3 开始,可以使用通配符 *
和 ?
进行类似全局的模式匹配。对于包含选定依赖项中哪些类的细粒度控制,可以使用工件过滤器:
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<filters>
<filter>
<artifact>junit:junit</artifact>
<includes>
<include>junit/framework/**</include>
<include>org/junit/**</include>
</includes>
<excludes>
<exclude>org/junit/experimental/**</exclude>
<exclude>org/junit/runners/**</exclude>
</excludes>
</filter>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
在这里,类似 Ant 的模式用于指定只包含 junit:junit
依赖关系中的某些类/资源到 Uber jar 中。第二个过滤器演示了在 1.3 版本的插件中使用通配符的工件标识。它从每个工件中排除所有与 jar 签名相关的文件,而不管其 groupId 或 artifactId 如何。
除了用户指定的过滤器外,该插件还可以配置为自动删除项目未使用的所有依赖类,从而最大限度地减少产生的 Uber jar 文件的大小:
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<minimizeJar>true</minimizeJar>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
从版本 1.6 开始,<minimizeJar>
将尊重那些被特别标记为包含在过滤器中的类。请注意,为工件中的类指定 include 过滤器会隐式地排除该工件中所有未指定的类, <excludeDefaults>false<\excludeDefaults>
将覆盖此行为,因此所有未指定的类仍将包括在内。
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<minimizeJar>true</minimizeJar>
<filters>
<filter>
<artifact>log4j:log4j</artifact>
<includes>
<include>**</include>
</includes>
</filter>
<filter>
<artifact>commons-logging:commons-logging</artifact>
<includes>
<include>**</include>
</includes>
</filter>
<filter>
<artifact>foo:bar</artifact>
<excludeDefaults>false</excludeDefaults>
<includes>
<include>foo/Bar.class</include>
</includes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
修改类的包名以避免冲突
如果 Uber jar 被重用为其他项目的依赖项,那么直接将工件依赖项中的类包含在 Uber jar 中可能会由于类路径上的重复类而导致类加载冲突。为了解决这个问题,可以重新定位着色工件中包含的类(即修改 class 文件的包名),以便创建其字节码的私有副本:
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<relocations>
<relocation>
<pattern>org.codehaus.plexus.util</pattern>
<shadedPattern>org.shaded.plexus.util</shadedPattern>
<excludes>
<exclude>org.codehaus.plexus.util.xml.Xpp3Dom</exclude>
<exclude>org.codehaus.plexus.util.xml.pull.*</exclude>
</excludes>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
这指示插件通过移动相应的 jar 文件条目并重写受影响的字节码,将类从包 org.codehaus.plexus.util 及其子包移动到包 org.shade.plexus.util 中。类 Xpp3Dom和其他一些类将保留在其原始包中。还可以使用 <include>
元素来缩小模式范围:
<project>
...
<relocation>
<pattern>org.codehaus.plexus.util</pattern>
<shadedPattern>org.shaded.plexus.util</shadedPattern>
<includes>
<include>org.codehaud.plexus.util.io.*</include>
</includes>
</relocation>
...
</project>
附加着色工件
默认情况下,插件将用着色工件替换项目的主工件。如果原始工件和着色工件都应安装/部署到仓库,则可以配置该插件以将着色工件作为次要工件附加:
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<shadedArtifactAttached>true</shadedArtifactAttached>
<shadedClassifierName>jackofall</shadedClassifierName> <!-- Any name that makes sense -->
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
着色工件通过额外的 classifier 来与主工件进行区分。
Executable Jar
要创建可执行的 Uber jar,只需设置用作应用程序入口点的主类:
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.sonatype.haven.HavenCli</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
这个代码段配置了一个特殊的资源转换器,它在着色 jar 的 MANIFEST.MF 中设置 Main-Class
条目。其他条目也可以通过 <manifestEntries>
部分中的键值对添加到 MANIFEST.MF 文件中:
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Main-Class>org.sonatype.haven.ExodusCli</Main-Class>
<Build-Number>123</Build-Number>
</manifestEntries>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
资源转换
只要不存在重叠,将多个工件中的类/资源聚合到一个 Uber jar 中是很简单的。否则,需要某种逻辑来合并来自多个 jar 的资源。这就是资源转换器的作用所在。
下表是 org.apache.maven.plugins.shade.resource
包中的资源转换器。
资源转换器类名 | 描述 |
---|---|
ApacheLicenseResourceTransformer | 防止许可证重复 |
ApacheNoticeResourceTransformer | 准备合并 NOTICE |
AppendingTransformer | 向一个资源中添加内容 |
ComponentsXmlResourceTransformer | 聚合 Plexus components.xml
|
DontIncludeResourceTransformer | 阻止匹配资源的包含 |
GroovyResourceTransformer | 合并 Apache Groovy 扩展模块 |
IncludeResourceTransformer | 从项目中添加文件 |
ManifestResourceTransformer | 设置 MANIFEST 中的条目 |
PluginXmlResourceTransformer | 合并 Maven plugin.xml
|
ResourceBundleAppendingTransformer | 合并 ResourceBundles |
ServicesResourceTransformer | 重新修改 META-INF/services 资源中的类名并将它们合并 |
XmlAppendingTransformer | 项 XML 资源文件中添加 XML 内容 |
下表是 org.apache.maven.plugins.shade.resource.properties
包中的资源转换器(从 3.2.2 版本开始可用)。
资源转换器类名 | 描述 |
---|---|
PropertiesTransformer | 合并拥有序号的 properties 文件以解决冲突 |
OpenWebBeansPropertiesTransformer | 合并 Apache OpenWebBeans 配置文件 |
MicroprofileConfigTransformer | 根据序号合并冲突的 Microprofile Config property |
使用 ComponentsXmlResourceTransformer 合并 Plexus 组件描述符
作为 Plexus IoC 容器组件的 jar 包含一个 META-INF/Plexus/components.xml
条目,该条目声明组件及其需求。如果 Uber jar 聚合了多个 Plexus 组件,则需要使用 ComponentXmlResourceTransformer
来合并 XML 描述符:
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ComponentsXmlResourceTransformer"/>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
从 1.3 版本开始,该资源转换器还将更新描述符,以说明组件接口/实现(如果有)的重新定位。
使用 PluginXmlResourceTransformer 重新定位 Maven 插件描述符的类
随着 Plugin Tools 3.0 的引入。现在对类的引用不再是作为字符串的类名,而是实际的类引用。当您想要重新定位类时,您必须手动维护 META-INF/maven/plugin.xml
,但现在可以使用 PluginXmlResourceTransformer
来完成。
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.PluginXmlResourceTransformer"/>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
使用 ServicesResourceTransformer 拼接服务条目
提供某些接口实现的 jar 文件通常附带 META-INF/services/
目录,该目录将接口映射到它们的实现类,以供服务定位器查找。要重新定位这些实现类的类名,并将同一接口的多个实现合并到一个服务条目中,可以使用 ServicesResourceTransformer:
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
使用 AppendingTransformer、XmlAppendingTransformer 和 ResourceBundleAppendingTransformer 合并特定的文件
某些 jar 包含具有相同文件名的其他资源(如 properties 文件)。为了避免覆盖同名文件,您可以选择通过将其内容附加到一个文件中来合并它们。一个很好的例子是当聚合 spring-context 和 plexus-spring 的 jar 包时,它们都有 META-INF/spring.handlers
文件,Spring 使用该文件处理 XML Schema 命名空间。您可以使用 AppendingTransformer
合并具有该特定名称的所有文件的内容,如下所示:
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.handlers</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.schemas</resource>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
对于 XML 文件,可以使用 XmlAppendingTransformer
:
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.XmlAppendingTransformer">
<resource>META-INF/magic.xml</resource>
<!-- Add this to enable loading of DTDs
<ignoreDtd>false</ignoreDtd>
-->
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
从 1.3.1 版本开始,XmlAppendingTransformer
默认情况下不会加载 DTD,从而避免网络访问。这种模式的潜在缺点是无法解析可能导致转换失败的外部实体,例如,当使用某些 JRE 1.4 中使用的 Crimson XML 解析器时。如果转换后的资源使用外部实体,则可以重新启用 DTD 解析,或者将 xerces:xercesImpl:2.9.1
的插件依赖项添加到 POM 中。
对于 ResourceBundles properties 文件,您可以改用 ResourceBundleAppendingTransformer
,它也将尊重所有可用的本地化设置 :
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ResourceBundleAppendingTransformer">
<!-- the base name of the resource bundle, a fully qualified class name -->
<basename>path/to/Messages</basename>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
使用 DontIncludeResourceTransformer 排除资源
DontIncludeResourceTransformer
允许在资源名称以给定值结尾时排除资源。例如,以下示例排除以 .txt 结尾的所有资源。
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.DontIncludeResourceTransformer">
<resource>.txt</resource>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
从 maven-shade-plugin-3.0 开始,还可以提供不应包含的资源列表,如:
<transformer implementation="org.apache.maven.plugins.shade.resource.DontIncludeResourceTransformer">
<resources>
<resource>.txt</resource>
<resource>READ.me</resource>
</resources>
</transformer>
使用 IncludeResourceTransformer 添加新资源
IncludeResourceTransformer
允许项目文件以给定名称包含在包中。例如,以下示例在包中包含 README.txt,作为 META-INF 目录中的自述文件。
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.IncludeResourceTransformer">
<resource>META-INF/README</resource>
<file>README.txt</file>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
使用 ManifestResourceTransformer 设置清单条目
ManifestResourceTransformer 允许替换 MANIFEST 中的现有条目并添加新条目。例如,
-
app.main.class
property 的值的Main-Class
条目; -
maven.compile.source
property 的值是X-Compile-Source-JDK
条目; -
maven.compile.target
property 的值是X-Compile-Target-JDK
条目。
默认情况下,ManifestResourceTransformer
将重新定位以下 attribute:
- Export-Package
- Import-Package
- Provide-Capability
- Require-Capability
使用 additionalAttributes
,您还可以指定需要重新定位的 attribute。
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Main-Class>${app.main.class}</Main-Class>
<X-Compile-Source-JDK>${maven.compile.source}</X-Compile-Source-JDK>
<X-Compile-Target-JDK>${maven.compile.target}</X-Compile-Target-JDK>
</manifestEntries>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
使用 ApacheLicenseResourceTransformer 来防止许可证重复
一些开源生产者(包括 Apache 软件基金会)会在 META-INF 目录中包含其许可证副本,它们通常被命名为 LICENSE 或 LICENSE.txt。合并这些依赖项时,添加这些资源可能会导致许可证文件冲突。ApacheChenseResourceTransformer
确保不会合并重复的许可证(根据该约定命名)。例如,以下内容可防止将 commons-collections
依赖项的许可证出现合并冲突。
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ApacheLicenseResourceTransformer">
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
使用 ApacheNoticeResourceTransformer 来聚合 Notice
一些许可证(包括 Apache License, Version 2)要求下游发布者必需保留该许可对外的一些 NOTICE
。ApacheNoticeResourceTransformer
自动组装适当的 NOTICE
。例如,要简单地合并到依赖的告知中请使用如下配置:
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ApacheNoticeResourceTransformer">
<addHeader>false</addHeader>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
使用 GroovyResourceTransformer 聚合 Apache Groovy 扩展模块描述符
Apache Groovy 语言在 META-INF/services/org.codehaus.groovy.runtime.ExtensionModule
目录提供了扩展模块,这些模块使用 property 文件格式。GroovyResourceTransformer
自动组装 Groovy 扩展模块的 NOTICE
。例如,简单地合并几个 jar 的扩展模块如下:
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.GroovyResourceTransformer">
<extModuleName>the-aggregated-module</extModuleName>
<extModuleVersion>1.0.0</extModuleVersion>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
使用 PropertiesTransformer 来合并 properties 文件
PropertiesTransformer
允许合并一组 properties 文件,并根据赋予每个文件优先级的序号解决冲突。可选的 alreadyMergedKey
允许在文件中使用布尔标志,如果该标志设置为 true,则请求将文件作为合并的结果使用。如果两个文件在合并过程中被认为是完整的,则着色将失败。
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.properties.PropertiesTransformer">
<!-- required configuration -->
<resource>configuration/application.properties</resource>
<ordinalKey>ordinal</ordinalKey>
<!-- optional configuration -->
<alreadyMergedKey>already_merged</alreadyMergedKey>
<defaultOrdinal>0</defaultOrdinal>
<reverseOrder>false</reverseOrder>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
使用 OpenWebBeansPropertiesTransformer 来合并 Apache OpenWebBeans 配置
OpenWebBeansPropertiesTransformer
为 Apache OpenWebBeans 配置文件预配置一个 PropertiesTransformer
。
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.properties.OpenWebBeansPropertiesTransformer" />
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
使用 MicroprofileConfigTransformer 来合并 Microprofile Config properties
MicropFileConfigTransformer
为 Microprofile Config 预配置一个 PropertiesTransformer
。唯一需要的配置是序号。支持 alreadyMergedKey
,但规范中未定义。
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.properties.MicroprofileConfigTransformer">
<resource>configuration/app.properties</resource>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
使用自定义的着色器实现
默认情况下,该插件提供 DefaultShader 实现,但在版本1.6中,您可以使用自己的实现。
下面创建一个标准 Maven 项目来使用您自己的实现。
Dependency to Plexus annotations
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-component-annotations</artifactId>
<version>1.5.5</version>
</dependency>
Create your Shader
@Component( role = Shader.class, hint = "mock" )
public class MockShader implements Shader {
// implement the interface here
}
// Use the plexus component metadata plugin in your job to generate Plexus metadata
<plugin>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-component-metadata</artifactId>
<version>1.5.5</version>
<executions>
<execution>
<goals>
<goal>generate-metadata</goal>
</goals>
</execution>
</executions>
</plugin>
假设您的项目坐标为 org.foo.bar:wine:1.0
,则必须将其添加为着色插件的依赖项。
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<dependencies>
<dependency>
<groupId>org.foo.bar</groupId>
<artifactId>wine</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<shaderHint>mock</shaderHint>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
现在,mojo 将使用您自己的实现。