1. 程式人生 > >將 Spring boot 專案打成可執行Jar包,及相關注意事項(main-class、缺少 xsd、重複打包依賴)

將 Spring boot 專案打成可執行Jar包,及相關注意事項(main-class、缺少 xsd、重複打包依賴)

最近在看 spring boot 的東西,覺得很方便,很好用。對於一個簡單的REST服務,都不要自己部署Tomcat了,直接在 IDErun 一個包含 main 函式的主類就可以了。

但是,轉念一想,到了真正需要部署應用的時候,不可能通過 IDE 去部署啊。那有沒有辦法將 spring boot 的專案打包成一個可執行的 jar 包,然後通過 java -jar 命令去啟動相應的服務呢?

很明顯,是有的。下面,我把我自己的實踐過程及遇到的問題,一 一說明一下。

首先,把專案的 POM 配置檔案的雛形放上來
PS: (程式碼我就不放上來了,spring boot 官網上有。我在本文的最下面會給出連結。)

<?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">
    <modelVersion>4.0.0</modelVersion>
<artifactId>spring-boot</artifactId> <version>0.1-SNAPSHOT</version> <name>spring-boot</name> <packaging>jar</packaging> <parent> <groupId>org.rainbow</groupId> <artifactId>spring</artifactId>
<version>0.1-SNAPSHOT</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> </project>

這裡,我沒有使用 spring boot 預設的 parent 工程,而是使用自己專案的 parent 工程,具體請參見 我的另一篇Blog

只要有了上面的這段 pom 配置,你就可以在 IDE 裡啟動你的應用了。

下面,說明一下,將專案打成 可執行Jar包 所需要的配置。

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <mainClass>org.rainbow.spring.boot.Application</mainClass>
            </configuration>
            <executions>
                <execution>
                    <goals>
                        <goal>repackage</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

很簡單吧?我們只需要新增一個 spring-boot-maven-plugin 外掛就可以解決問題了。

加了這個外掛之後,我們可以通過下面的方式來將專案打成可執行jar包。

mvn clean package

請注意,從我們上面的配置來看,雖然我們沒有明確寫出將外掛的 repackage 這個 goal 繫結到了 maven 的哪個 life cycle 上,但是外掛本身預設將它繫結到了 maven 的 package 上。

所以,只有當我們執行的 maven 命令會觸發 package 這個life cycle 時,上面的外掛才會被觸發。

另外,我們可以在上面的 pom 配置中,去掉下面這段配置:

<executions>
    <execution>
        <goals>
            <goal>repackage</goal>
        </goals>
    </execution>
</executions>

然後,我們可以通過手動來執行外掛的 repackage 這個 goal。

mvn clean package spring-boot:repackage

其中,spring-boot 是固定的字首。

從以上的描述來看,我們一共有兩種方式來啟用這個外掛,任選其一哦。

執行了這個外掛之後,你會在 target 目錄下發現兩個Jar包:

  • xxxxx.jar.original
  • xxxxx.jar

其中,第一個是僅僅包含我們專案原始碼的 Jar包,它是無法執行的。第二個是經由 spring boot maven plugin 重新包裝後的Jar包,這個是可以執行的。可以通過下面的命令來試下:

java -jar xxxxx.jar

然後,你應該會看到下面類似的啟動資訊:

SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/C:/Users/XXXXXXX/Desktop/spring-boot-0.1-SNAPSHOT.jar!/BOOT-INF/classes!/org/slf4j/impl/StaticLoggerBinder.class]

SLF4J: Found binding in [jar:file:/C:/Users/XXXXXXX/Desktop/spring-boot-0.1-SNAPSHOT.jar!/BOOT-INF/lib/logback-classic-1.1.9.jar!/org/slf4j/impl/StaicLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [ch.qos.logback.classic.util.ContextSelectorStaticBinder]

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::

2017-02-14 10:47:29.488  INFO 11860 --- [           main] org.rainbow.spring.boot.Application      : Starting Application on XXXXXXX-PC with PID 11860 (C:\Users\XXXXXXX\Desktop\spring-boot-0.1-SNAPSHOT.jar started by XXXXXXX in C:\Users\XXXXXXX\Desktop)
2017-02-14 10:47:29.494  INFO 11860 --- [           main] org.rainbow.spring.boot.Application      : No active profile set, falling back to default profiles: default
2017-02-14 10:47:29.607  INFO 11860 --- [           main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot[email protected]14514713: startup date [Tue Feb 14 10:47:29 CST 2017]; root of context hierarchy
2017-02-14 10:47:31.731  INFO 11860 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration' of type [class org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2017-02-14 10:47:31.849  INFO 11860 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'validator' of type [class org.springframework.validation.beanvalidation.LocalValidatorFactoryBean] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2017-02-14 10:47:32.673  INFO 11860 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http)
2017-02-14 10:47:32.699  INFO 11860 --- [           main] o.apache.catalina.core.StandardService   : Starting service Tomcat
2017-02-14 10:47:32.701  INFO 11860 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.5.11
2017-02-14 10:47:32.848  INFO 11860 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2017-02-14 10:47:32.848  INFO 11860 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 3244 ms

下面說明一下幾個注意事項。

第一個是有關於 main 函式的。

我們知道,一個jar包要能夠執行,那麼必須在其根目錄下的 META-INF 目錄下的 MANIFEST.MF 檔案中宣告 Main-Class 這個屬性。

對於 spring boot 的專案來說,這一點也是必須的。那麼,我們應該如何來宣告我們專案中的 main 函式所在的 class 呢?

方法有二。

1. 不作任何宣告

即,我們不新增任何的其他宣告。這樣一來,spring boot maven plugin 在打包時,會自動掃描整個專案的原始碼,並將掃描到的第一個包含 main 函式的 class 作為Jar包的 Main-Class

2. 在 plugin 的配置中增加一個配置

<configuration>
    <mainClass>org.rainbow.spring.boot.Application</mainClass>
</configuration>    

這樣的話,Application 這個class將作為Jar包的 Main-Class

但是,你會發現,在最終打好的Jar中, Application 這個class,它並不是作為 Main-Class 這個屬性的值,而是作為 Start-Class 屬性的值。

這個是由 spring boot 自己進行處理的,我們無須過多關注。
(其實,在打好的Jar中,我們去看一下其中的 MANIFEST.MF檔案,可以發現,它的 Main-Class 指定的值是 org.springframework.boot.loader.JarLauncher, spring boot 會通過這個類去間接的執行 Start-Class 指定的類,即我們的主類)

第二個問題是關於專案可能會報找不到 spring 的某些 XSD 檔案的。
PS:以下篇幅來自 Spring如何載入XSD檔案


說明:
這個問題,我在自己的專案中沒有遇到,但是在網上看到這個問題的描述及處理。為了防止專案以後遇到問題,我就在此一起列出來。

Start.

問題現象是:

org.xml.sax.SAXParseException: schema_reference.4: Failed to read schema document 'http://www.springframework.org/schema/beans/spring-beans-3.0.xsd', because 1) could not find the document; 2) the document could not be read; 3) the root element of the document is not <xsd:schema>.  

很顯然,spring xml配置檔案中指定的xsd檔案讀取不到了,原因多是因為斷網或spring的官網暫時無法連線導致的。 你可以通過在瀏覽器輸入xsd檔案的URL,如:http://www.springframework.org/schema/beans/spring-beans-2.0.xsd 進行確認。

為什麼會這樣呢?要想直正弄明白這一問題還需要從spring的XSD檔案載入機制談起。

你必須知道一點:spring在載入xsd檔案時總是先試圖在本地查詢xsd檔案(spring的jar包中已經包含了所有版本的xsd檔案),如果沒有找到,才會轉向去URL指定的路徑下載。

這是非常合理的做法,並不像看上去的那樣,每次都是從站點下載的。

事實上,假如你的所有配置是正確定的,你的工程完全可以在斷網的情況下啟動而不會報上面的錯誤。Spring載入xsd檔案的類是PluggableSchemaResolver,你可以檢視一下它的原始碼來驗證上述說法。

另外,你可以在log4j.xml檔案中加入:

<logger name="org.springframework.beans.factory.xml">  
    <level value="all" />  
</logger> 

通過日誌瞭解spring是何載入xsd檔案的。

接下來,問題就是為什麼spring在本地沒有找到需要的檔案,不得不轉向網站下載。關於這個問題,其實也非常簡單:

在很多spring的jar包裡,在META-INF目錄下都有一個spring.schemas,這是一個property檔案,其內容類似於下面:

http\://www.springframework.org/schema/beans/spring-beans-2.0.xsd=org/springframework/beans/factory/xml/spring-beans-2.0.xsd  
http\://www.springframework.org/schema/beans/spring-beans-2.5.xsd=org/springframework/beans/factory/xml/spring-beans-2.5.xsd  
http\://www.springframework.org/schema/beans/spring-beans-3.0.xsd=org/springframework/beans/factory/xml/spring-beans-3.0.xsd  
....  

實際上,這個檔案就是spring關於xsd檔案在本地存放路徑的對映,spring就是通過這個檔案在本地(也就是spring的jar裡)查詢xsd檔案的。

那麼,查詢不到的原因排除URL輸入有誤之外,可能就是宣告的xsd檔案版本在本地不存在。

一般來說,新版本的spring jar包會將過去所有版本(應該是自2.0以後)的xsd打包,並在spring.schemas檔案中加入了對應項,出現問題的情況往往是宣告使用了一個高版本的xsd檔案,如3.0,但依賴的spring的jar包卻是2.5之前的版本,由於2.5版本自然不可能包含3.0的xsd檔案,此時就會導致spring去站點下載目標xsd檔案,如遇斷網或是目標站點不可用,上述問題就發生了。

但是,在實現開發中,出現上述錯誤的機率並不高,最常見的導致這一問題的原因其實與使用了一個名為“assembly”的maven打包外掛有關。

很多專案需要將工程連同其所依賴的所有jar包打包成一個jar包,maven的assembly外掛就是用來完成這個任務的。但是由於工程往往依賴很多的jar包,而被依賴的jar又會依賴其他的jar包,這樣,當工程中依賴到不同的版本的spring時,在使用assembly進行打包時,只能將某一個版本jar包下的spring.schemas檔案放入最終打出的jar包裡,這就有可能遺漏了一些版本的xsd的本地對映,進而出現了文章開始提到的錯誤。

如果你的專案是打成單一jar的,你可以通過檢查最終生成的jar裡的spring.schemas檔案來確認是不是這種情況。

而關於這種情況,解決的方法一般是推薦使用另外一種打包外掛”shade“,它確實是一款比assembly更加優秀的工具,在對spring.schemas檔案處理上,shade能夠將所有jar裡的spring.schemas檔案進行合併,在最終生成的單一jar包裡,spring.schemas包含了所有出現過的版本的集合!

以上就是spring載入XSD檔案的機制和出現問題的原因分析。實際上,我們應該讓我們工程在啟動時總是載入本地的xsd檔案,而不是每次去站點下載,做到這一點就需要你結合上述提及的種種情況對你的工程進行一番檢查。

End.

好了,到此,我們瞭解了這個問題,並且知道了可以使用哪個外掛來避免這個問題。那麼,下面我們就說一下上面提及到的 shade 外掛如何配置吧。

我先直接將配置發上來吧:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>shade</goal>
            </goals>
            <configuration>
                <createDependencyReducedPom>true</createDependencyReducedPom>
                <dependencyReducedPomLocation>${project.build.directory}/dependency-reduced-pom.xml</dependencyReducedPomLocation>
                <minimizeJar>false</minimizeJar>
                <promoteTransitiveDependencies>false</promoteTransitiveDependencies>

                <transformers>
                    <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                        <resource>META-INF/spring.factories</resource>
                    </transformer>
                    <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.provides</resource>
                    </transformer>
                    <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                        <resource>META-INF/spring.schemas</resource>
                    </transformer>
                    <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                        <resource>META-INF/spring.tooling</resource>
                    </transformer>
                </transformers>
            </configuration>
        </execution>
    </executions>
</plugin>

從上面的配置來看,這個外掛也是在 maven 的 package 階段才會被觸發,與上面介紹的 spring boot maven plugin 是一樣的。

下面重點說明一下 transformer 這個標籤的作用。

上面雖然寫了 5 個 transformer,但其實都一樣,只不過是處理了5個不同的檔案而已:

  • META-INF/spring.factories
  • META-INF/spring.handlers
  • META-INF/spring.provides
  • META-INF/spring.schemas
  • META-INF/spring.tooling

下面 以 META-INF/spring.factories 為例進行說明。

上面的配置就是將所有被專案依賴的Jar包中的 META-INF/spring.factories 檔案合併到一份檔案中,這份檔案將作為最終的 Jar包 中的 META-INF/spring.factories 這個檔案。(名稱並沒有發生變化)。

其實,這個外掛還有一個 ManifestResourceTransformer,我們可以通過這個 transformer 來設定 Jar 的Main-Class 等屬性,如下:

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

這裡列出來的屬性,都將被寫入到 META-INF/MANIFEST.MF 檔案中。

不過,需要注意的一點是:雖然可以通過此 transformer 來設定 Jar包的 Main-Class,但是此處設定的值將會被在spring boot maven plugin 設定的 Main-Class 的值所替代掉。因為 spring boot maven plugin 外掛是在 apache maven shade plugin 之後執行的。

第三個問題,是關於專案重複引入依賴包的問題。


2017.3.18 補充:
經過最近的測試,我個人覺得,只需要使用 spring-boot-maven 這個外掛就可以了。因為這個外掛會將所有依賴的 jar 打到最終的jar裡去,並不會發生上面問題二中所說的: xld 中元素變少的情況。
而這第三個問題,就是由於上面使用了 shade 外掛導致的。所以,如果你只使用了 spring-boot-maven 的外掛的話,問題二 和 問題三 都無視吧。。。

如果細心的話,我們會發現上面有這麼一段輸出:

SLF4J: Class path contains multiple SLF4J bindings.

SLF4J: Found binding in [jar:file:/C:/Users/XXXXXXX/Desktop/spring-boot-0.1-SNAPSHOT.jar!/BOOT-INF/classes!/org/slf4j/impl/StaticLoggerBinder.class]

SLF4J: Found binding in [jar:file:/C:/Users/XXXXXXX/Desktop/spring-boot-0.1-SNAPSHOT.jar!/BOOT-INF/lib/logback-classic-1.1.9.jar!/org/slf4j/impl/StaicLoggerBinder.class]

SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.

SLF4J: Actual binding is of type [ch.qos.logback.classic.util.ContextSelectorStaticBinder]

意思就是說,在 classpath 中發現了兩個 SLF4J 的繫結:

  • BOOT-INF/classes!/org/slf4j/impl/StaticLoggerBinder.class
  • BOOT-INF/lib/logback-classic-1.1.9.jar!/org/slf4j/impl/StaicLoggerBinder.class

這麼看來,應該是 StaticLoggerBinder.class 被引入了兩次。下面,我們看一下經過 spring boot maven 外掛打包好的jar包,在解壓之後的資料夾結構是怎麼樣的。請看:

這裡寫圖片描述

我們看到,BOOT-INF 目錄下的 classlib 目錄下,幾乎所有的依賴都被分別匯入了一份。那這個結構的是怎麼來的呢?大概下面這樣的:

  • 整個打包過程,是先執行 maven shade 外掛,將專案依賴的所有jar的class檔案抽取出來做成一個 fat jar,它生的jar包的結構(假設名為 1.jar, 該名稱下面會使用到),大概如下圖所示:

這裡寫圖片描述

  • 然後,再執行 spring boot maven 外掛(為方便描述,假設該步驟生成的jar包名稱為 2.jar),將上面生成的 1.jar 中的 META-INF 資料夾作為 2.jarMETA-INF1.jar 中的其他檔案,全部移至 2.jar 中的 BOOT-INF/class 資料夾下
  • 同時,spring boot maven 外掛會將專案所有依賴的所有jar包,再次打包進 2.jarBOOT-INF/lib 下。
  • 另外,spring boot 會將 spring boot loader 的 class 檔案放至 2.jar 的根目錄下(上上個圖的中最後一個名為org的目錄),用於啟動jar包。

好了,既然現在知道問題發生在哪裡了,那就想辦法去掉其中的一個唄?那該如何去掉呢?我經過一些調查與測試之後發現,只能在 shade 外掛中增加相關配置來過濾掉 class 目錄下的重複的類。原因有以下幾點:

  • 我們需要使用 maven shade 外掛來避免上面說到的 xsd 的問題
  • spring boot maven 外掛雖然提供了 excludeArtifactIdsexcludeGroupIdsexcludes 屬性來配置需要排除的 依賴,但是它只能完全匹配,不能使用 * 或者 ? 這兩個萬用字元進行模糊匹配,所以這幾個屬性只適用於要排除個別依賴的情景。

下面,我們來看下如何配置 maven shade 的外掛來避免重複引用依賴的問題:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>shade</goal>
            </goals>
            <configuration>
                <createDependencyReducedPom>true</createDependencyReducedPom>
                <dependencyReducedPomLocation>${project.build.directory}/dependency-reduced-pom.xml</dependencyReducedPomLocation>
                <minimizeJar>false</minimizeJar>
                <promoteTransitiveDependencies>false</promoteTransitiveDependencies>

                <transformers>
                    <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                        <resource>META-INF/spring.factories</resource>
                    </transformer>
                    <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.provides</resource>
                    </transformer>
                    <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                        <resource>META-INF/spring.schemas</resource>
                    </transformer>
                    <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                        <resource>META-INF/spring.tooling</resource>
                    </transformer>
                </transformers>

                <!-- use filter to include only the needed files -->
                <filters>
                    <filter>
                        <artifact>*:*</artifact>
                        <includes>
                            <include>*</include>
                            <include>META-INF/**</include>
                            <include>org/rainbow/**</include>
                        </includes>
                    </filter>
                </filters>
            </configuration>
        </execution>
    </executions>
</plugin>

重點是最後面的 filter 屬性的配置。我這麼配置的作用是:

  • 保留jar包根目錄下的所有檔案
  • 保留jar包META-INF目錄及其子目錄下的所有檔案
  • 保留jar包org/rainbow/目錄及其子目錄下的所有檔案 (org/rainbow/ 是我自己寫的程式碼的package的字首,通過該規則來保留我自己的原始碼)
  • 對所有依賴,執行上面三個過濾

當然,對於某些特殊的jar包,上面的這個規則列表可能還不完善,需要根據實際情況進行修改。

最後,給出專案的完整 POM 配置:

<?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">
    <modelVersion>4.0.0</modelVersion>

    <artifactId>spring-boot</artifactId>
    <version>0.1-SNAPSHOT</version>
    <name>spring-boot</name>

    <packaging>jar</packaging>

    <parent>
        <groupId>org.rainbow</groupId>
        <artifactId>spring</artifactId>
        <version>0.1-SNAPSHOT</version>
    </parent>

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-loader</artifactId>
            <version>1.5.1.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>logback-classic</artifactId>
                    <groupId>ch.qos.logback</groupId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>2.7</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <createDependencyReducedPom>true</createDependencyReducedPom>
                            <dependencyReducedPomLocation>${project.build.directory}/dependency-reduced-pom.xml</dependencyReducedPomLocation>
                            <minimizeJar>false</minimizeJar>
                            <promoteTransitiveDependencies>false</promoteTransitiveDependencies>

                            <transformers>
                                <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                                    <resource>META-INF/spring.factories</resource>
                                </transformer>
                                <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.provides</resource>
                                </transformer>
                                <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                                    <resource>META-INF/spring.schemas</resource>
                                </transformer>
                                <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                                    <resource>META-INF/spring.tooling</resource>
                                </transformer>
                            </transformers>

                            <!-- use filter to include only the needed files -->
                            <filters>
                                <filter>
                                    <artifact>*:*</artifact>
                                    <includes>
                                        <include>*</include>
                                        <include>META-INF/**</include>
                                        <include>org/rainbow/**</include>
                                    </includes>
                                </filter>
                            </filters>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>org.rainbow.spring.boot.Application</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

參考文件