[Maven] maven插件系列之maven-shade-plugin

[Maven] maven插件系列之maven-shade-plugin

0 序言/背景

  • 最近兩天遇到一個【包衝突】的坑:
common-resource-sdk 工程
  依賴: nacos-client
    依賴: http-client:4.5.3

bdp-business-data-integration-service 工程
  依賴: common-resource-sdk
  依賴: elasticsearch-rest-client
    依賴: http-client:4.5.10

bdp-business-data-integration-service工程中排除了common-resourcehttp-client包,但其打出的JAR包中依舊含有http-client:4.5.3

爲何排包失敗了呢?

本質原因:common-resurce打包方式是jar-with-dependencies

  • 基於此,本文好好聊聊maven打包插件:maven-shade-plugin

1 插件簡述/Plugin Overview

1.1 定義與目的/Definition & Goals

  • Official Definition

Apache Maven : maven-shade-plugin
This plugin provides the capability to package the artifact in an uber-jar, including its dependencies and to shade - i.e. rename - the packages of some of the dependencies.(該插件提供了將工件打包到uber jar中的功能,包括其依賴項,並對一些依賴項的包進行着色(即重命名)。)

  • Goals Overview

The Shade Plugin has a single goal: (Shade插件只有一個目標:)
shade:shade is bound to the package phase and is used to create a shaded jar. (shade:shade綁定到封裝階段,用於創建一個shaded jar。)

  • Sample Explain/簡單解釋:

maven-plugin-shade 插件提供了2個能力:
把整個項目(包含它的依賴)都打包到一個 “uber-jar” 中 shade - 即重命名某些依賴的包。也由此引出了兩個問題:
+ 什麼是 uber-jar ?
+ 這中打包後帶依賴的 Jar 包一般稱爲 uber-jar 或 fat-jar 或者 jar-with-dependencies;意思就是包含依賴的 jar。
+ 什麼是 shade ?
+ shade 意爲遮擋,在此處可理解爲:對依賴的 jar 包的重定向(主要通過重命名的方式)。

shade
	n. 燈罩;陰涼處;(樹)蔭;色度;痕跡,影子,遺風;一點;差別;背陰;暗部;陰魂;濃淡深淺
	vt. 給…遮擋(光線);畫陰影;加燈罩;把…塗暗;險勝

uber
	adj.超級的;極其的;最好的;

1.2 版本/Version

If you like to use minimizeJar this means you have to use JDK8+. This is based on a required upgrade of dependencies.
如果你喜歡使用minimizeJar,這意味着你必須使用JDK8+。這是基於所需的依賴項升級。

Latest version(最新版本) = 3.5.0

https://mvnrepository.com/artifact/org.apache.maven.plugins/maven-shade-plugin

2 實踐與使用/Usage

2.1 官方插件說明

Official Document/官方文檔

General instructions on how to use the Shade Plugin can be found on the usage page. Some more specific use cases are described in the examples given below.
有關如何使用Shade插件的一般說明可以在使用頁面上找到。下面給出的示例中描述了一些更具體的用例。

In case you still have questions regarding the plugin's usage, please feel free to contact the user mailing list. The posts to the mailing list are archived and could already contain the answer to your question as part of an older thread. Hence, it is also worth browsing/searching the mail archive.
如果您對插件的使用仍有疑問,請隨時聯繫用戶郵件列表。郵件列表中的帖子已存檔,可能已經包含了您問題的答案,作爲舊線程的一部分。因此,瀏覽/搜索郵件檔案也是值得的。

If you feel like the plugin is missing a feature or has a defect, you can fill a feature request or bug report in our issue tracker. When creating a new issue, please provide a comprehensive description of your concern. Especially for fixing bugs it is crucial that the developers can reproduce your problem. For this reason, entire debug logs, POMs or most preferably little demo projects attached to the issue are very much appreciated. Of course, patches are welcome, too. Contributors can check out the project from our source repository and will find supplementary information in the guide to helping with Maven.
如果你覺得插件缺少功能或有缺陷,你可以在我們的問題跟蹤器中填寫功能請求或錯誤報告。創建新問題時,請對您關心的問題進行全面描述。特別是對於修復錯誤,開發人員能夠重現您的問題是至關重要的。出於這個原因,非常感謝整個調試日誌、POM,或者最好是附加到該問題的小演示項目。當然,補丁也是受歡迎的。參與者可以從我們的源代碼庫中查看該項目,並在幫助使用Maven的指南中找到補充信息。

2.2 基本使用 : build>plugin

2.2.0 maven-shade-plguin 的基本使用

  • maven-plugin-shade 必須和 Maven 構建生命週期中的 package 階段綁定。

也就是說,當執行 mvn package 時會自動觸發 shade

  • 要使用 maven-shade-plugin,只需要在 pom.xml<plugins> 標籤下添加它的配置即可,示例如下:
<project>
    //...
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.2.4</version>
                <configuration>
                    <!-- 此處按需編寫更具體的配置 -->
                </configuration>
                <executions>
                    <execution>
                        <!-- 和 package 階段綁定 -->
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
    
    // ...
</project>

默認情況下,會把項目所有的依賴都包含進最終的 jar 包中。當然,我們也可在 <configuration> 標籤內配置更具體的規則。

2.2.1 測試驗證(maven-shade-plugin)

  • pom.xml
<?xml version="1.0" encoding="UTF-8"?>  
<project xmlns="http://maven.apache.org/POM/4.0.0"  
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">  
<!--  
    <parent>        
        <artifactId>johnny-webapp-quickstart</artifactId>        
        <groupId>cn.johnnyzen</groupId>        
        <version>1.0.0-SNAPSHOT</version>    
    </parent>
-->  
    <modelVersion>4.0.0</modelVersion>  
  
    <version>1.0.0-SNAPSHOT</version>  
    <groupId>cn.johnnyzen</groupId>  
    <artifactId>study-maven</artifactId>  
  
    <properties>  
        <maven.compiler.source>8</maven.compiler.source>  
        <maven.compiler.target>8</maven.compiler.target>  
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>  
        <maven-shade-plugin.version>3.5.0</maven-shade-plugin.version>  
  
        <fastjson.version>2.0.3</fastjson.version>  
    </properties>  
  
    <dependencies>  
        <dependency>  
            <groupId>com.alibaba</groupId>  
            <artifactId>fastjson</artifactId>  
            <version>${fastjson.version}</version>  
        </dependency>  
    </dependencies>  
  
    <build>  
        <plugins>  
            <plugin>  
                <groupId>org.apache.maven.plugins</groupId>  
                <artifactId>maven-shade-plugin</artifactId>  
                <version>${maven-shade-plugin.version}</version>  
                <configuration>  
                    <!-- 此處按需編寫更具體的配置 -->  
                </configuration>  
                <executions>  
                    <execution>  
                        <!-- 和 package 階段綁定 -->  
                        <phase>package</phase>  
                        <goals>  
                            <goal>shade</goal>  
                        </goals>  
                    </execution>  
                </executions>  
            </plugin>  
        </plugins>  
    </build>  
</project>
  • 打包結果
mvn clean install

2.2.2 測試驗證(maven-compile-plugin)

  • pom.xml
<?xml version="1.0" encoding="UTF-8"?>  
<project xmlns="http://maven.apache.org/POM/4.0.0"  
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">  
<!--  
    <parent>        
        <artifactId>johnny-webapp-quickstart</artifactId>        
        <groupId>cn.johnnyzen</groupId>        
        <version>1.0.0-SNAPSHOT</version>    
    </parent>
-->  
    <modelVersion>4.0.0</modelVersion>  
  
    <version>1.0.0-SNAPSHOT</version>  
    <groupId>cn.johnnyzen</groupId>  
    <artifactId>study-maven</artifactId>  
  
    <properties>  
        <java.jdk.version>1.8</java.jdk.version>  
        <maven.compiler.source>8</maven.compiler.source>  
        <maven.compiler.target>8</maven.compiler.target>  
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>  
  
        <maven-shade-plugin.version>3.5.0</maven-shade-plugin.version>  
        <maven-compiler-plugin.version>3.8.0</maven-compiler-plugin.version>  
        <fastjson.version>2.0.3</fastjson.version>  
    </properties>  
  
    <dependencies>  
        <dependency>  
            <groupId>com.alibaba</groupId>  
            <artifactId>fastjson</artifactId>  
            <version>${fastjson.version}</version>  
        </dependency>  
    </dependencies>  
  
    <build>  
        <plugins>  
            <plugin>  
                <groupId>org.apache.maven.plugins</groupId>  
                <artifactId>maven-compiler-plugin</artifactId>  
                <version>${maven-compiler-plugin.version}</version>  
                <configuration>  
                    <source>${java.jdk.version}</source>  
                    <target>${java.jdk.version}</target>  
                    <!--<encoding>${project.build.outputEncoding}</encoding>-->  
                    <!-- <skipTests>true</skipTests> --><!-- 跳過測試 -->  
                    <!--<verbose>true</verbose>-->                    <!--<showWarnings>true</showWarnings>-->                    <!--<fork>true</fork>--><!-- 要使compilerVersion標籤生效,還需要將fork設爲true,用於明確表示編譯版本配置的可用 -->  
                    <!--<executable>--><!-- path-to-javac --><!--</executable>--><!-- 使用指定的javac命令,例如:<executable>${JAVA_1_4_HOME}/bin/javac</executable> -->  
                    <!--<compilerVersion>${java.version}</compilerVersion>--><!-- 指定插件將使用的編譯器的版本 -->  
                    <!--<meminitial>128m</meminitial>--><!-- 編譯器使用的初始內存 -->  
                    <!--<maxmem>512m</maxmem>--><!-- 編譯器使用的最大內存 -->  
                    <!--<compilerArgument>-verbose -bootclasspath ${java.home}\lib\rt.jar</compilerArgument>--><!-- 這個選項用來傳遞編譯器自身不包含但是卻支持的參數選項 -->  
                </configuration>  
            </plugin>  
        </plugins>  
    </build>  
</project>
  • 打包效果
mvn clean install

2.2.3 測試驗證(build is empty)

  • pom.xml
<?xml version="1.0" encoding="UTF-8"?>  
<project xmlns="http://maven.apache.org/POM/4.0.0"  
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">  
<!--  
    <parent>        
        <artifactId>johnny-webapp-quickstart</artifactId>        
        <groupId>cn.johnnyzen</groupId>        
        <version>1.0.0-SNAPSHOT</version>    
    </parent>
-->  
    <modelVersion>4.0.0</modelVersion>  
  
    <version>1.0.0-SNAPSHOT</version>  
    <groupId>cn.johnnyzen</groupId>  
    <artifactId>study-maven</artifactId>  
  
    <properties>  
        <java.jdk.version>1.8</java.jdk.version>  
        <maven.compiler.source>8</maven.compiler.source>  
        <maven.compiler.target>8</maven.compiler.target>  
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>  
  
        <maven-shade-plugin.version>3.5.0</maven-shade-plugin.version>  
        <maven-compiler-plugin.version>3.8.0</maven-compiler-plugin.version>  
        <fastjson.version>2.0.3</fastjson.version>  
    </properties>  
  
    <dependencies>  
        <dependency>  
            <groupId>com.alibaba</groupId>  
            <artifactId>fastjson</artifactId>  
            <version>${fastjson.version}</version>  
        </dependency>  
    </dependencies>  
  
    <build>  
    </build>  
</project>
  • 打包效果
mvn clean install

2.3 擴展配置: build>plugin>configuration

2.3.1 include/exclude : 按需選擇要添加到最終 jar 包中依賴

  • 支持include/exclude 2 種操作
  • 支持執行通配符匹配目標依賴JAR包: '*' 、'?'
  • 配置格式:
groupId:artifactId[[:type]:classfier]
  • 樣例配置
<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>

2.3.2 filter : 過濾

  • 使用 <filters> 結合 <includes> & <excludes> 標籤可實現更靈活的依賴選擇。
  • 樣例配置:
<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>

2.3.3 minimizeJar : 最小化JAR包體積

  • 除了可以通過自定義的 filters 來過濾依賴,此插件還支持自動移除項目中沒有使用到的依賴,以此來最小化 jar 包的體積,只需要添加一項配置即可。示例如下:
<configuration>
    <minimizeJar>true</minimizeJar>
</configuration>

2.3.4 relocations : 重定位 class 文件

如果最終的 jar 包被其他的項目所依賴的話,直接地引用此 jar 包中的類可能會導致類加載衝突,這是因爲 classpath 中可能存在重複的 class 文件。爲了解決這個問題,我們可以使用 shade 提供的重定位功能,把部分類移動到一個全新的包中。示例如下:

<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>
涉及標籤:
	<pattern>:原始包名
	<shadedPattern>:重命名後的包名
	<excludes>:原始包內不需要重定位的類,類名支持通配符

例如,在上述示例中,我們把 org.codehaus.plexus.util 包內的所有子包及 class 文件(除了 ~.xml.Xpp3Dom 和 ~.xml.pull 包下的所有 class 文件)重定位到了 org.shaded.plexus.util 包內。

當然,如果包內的大部分類我們都不需要,一個個排除就顯得很繁瑣了。
此時我們也可以使用 <includes> 標籤來指定我們僅需要的類,示例如下:

<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>

2.3.5 mainClass:生成可執行 jar 包

  • 使用 maven-shade-plugin 後,最終生成的 jar 包可以包含所有項目所需要的依賴。
    我們會想,能不能直接運行這個 uber-jar 呢?答案是當然可以,並且十分簡單,只需要指定 <mainClass> 啓動類就可以了。
  • 示例如下:
<project>
    ...
    <configuration>
        <transformers>
            <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                <mainClass>org.sonatype.haven.HavenCli</mainClass>
            </transformer>
        </transformers>
    </configuration>
    ...
</project>

熟悉 jar 包的朋友們都知道,jar 包中默認會包含一個 MANIFEST.MF 文件,裏面描述了一些 jar 包的信息
使用 java 自帶的 jar 命令打包的時候可以指定 MANIFEST.MF,其中也可以指定 Main-Class 來使得 jar 包可運行。
那麼使用 shade 來指定和直接在 MANIFEST.MF 文件中指定有什麼區別呢?

答案是沒有區別,細心的讀者會發現 <mainClass> 標籤的父標籤是 <transformer> 有一個 implementation 屬性,其值爲 “~.ManifestResourceTransformer”,意思是 Manifest 資源文件轉換器。
上述示例只自指定了啓動類,因此 shade 會爲我們自動生成一個包含 Main-Class 的 MANIFEST.MF 文件,然後在打 jar 包時指定這個文件。

那如果我們想要完全定製 MANIFEST.MF 文件內容怎麼辦呢?我們可以使用 <manifestEntries> 標籤,示例如下:

<project>
    ...
    <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>
    ...
</project>

可執行jar包通過 java  -jar  命令啓動

2.3.6 生成資源文件

項目中涉及到的依賴可能會有它們所必需的資源文件,使用 shade 可以把它們聚合在同一個 jar 包中。
默認地,shade 爲我們提供了 12 個 ResourceTransformer 類:

類名 作用
ApacheLicenseResourceTransformer 防止 LICENSE 文件重複
ApacheNoticeResourceTransformer 準備合併的 NOTICE
AppendingTransformer 爲某個資源文件附加內容
ComponentsXmlResourceTransformer 聚合 Plexus components.xml
DontIncludeResourceTransformer 防止包含指定的資源
GroovyResourceTransformer 合併 Apache Groovy 的擴展模塊
IncludeResourceTransformer 添加項目中的文件爲資源文件
ManifestResourceTransformer 自定義 MANIFEST 文件
PluginXmlResourceTransformer 聚合 Maven 的 plugin.xml 配置
ResourceBundleAppendingTransformer 合併 ResourceBundles
ServicesResourceTransformer 重定位且合併 META-INF/services 資源文件中的 class 文件
XmlAppendingTransformer 爲 XML 資源文件附加內容

如果上述 12 個類都不能夠滿足我們的需求,我們可以實現 shade 提供的接口,按需自定義一個 ResourceTransformer,實現方法詳見官網 Using your own Shader implementation。

X 參考文獻/Reference Document

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章