Maven Shade 插件详解

简介

该插件提供了将工件打包成 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 中,但其 scopeprovided;如果为 false,则删除依赖项。

  • <minimizeJar>。如果为 true,则依赖关系将在类级别上剥离,仅为工件所需的可传递外壳。注意:使用此功能需要 Java 1.5 或更高版本。

  • <outputDirectory>。Shaded 工件的输出目录。

  • <outputFile>。Shaded 工件的输出文件路径。设置此参数后,创建的归档文件既不会替换项目的主工件,也不会附加它。因此,此参数会导致使用时忽略参数 finalNameshadedArtifactAttachedshadedClassifierNamecreateDependencyReducedPom

  • <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 值时可以使用该参数。如果原始的 artifactIdfoo,那么最终的工件将类似于 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)要求下游发布者必需保留该许可对外的一些 NOTICEApacheNoticeResourceTransformer 自动组装适当的 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 将使用您自己的实现。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容