建立獨立的Java可執行JAR的三種方法 - frankel

發表於2021-01-15

在這篇文章中,我們描述了三種建立獨立的可執行JAR的方法。

當您的應用程式超出了十幾行程式碼時,您可能應該將程式碼分成多個類。在Java中,經典打包格式是Java ARchive,也稱為JAR。但是實際應用程式可能依賴於其他JAR包。

這篇文章旨在描述建立獨立的可執行JAR(也稱為uber-JAR或胖JAR)的方法。

 

什麼是可執行JAR?

JAR只是類檔案的集合。為了可執行,其META-INF/MANIFEST.MF檔案應指向實現該main()方法的類。您可以使用Main-Class屬性來執行此操作。這是一個例子:

Main-Class: path.to.MainClass

MainClass有一個static main(String…​ args)方法

 

處理類路徑

大多數應用程式依賴現有程式碼。Java提供了類路徑classpath的概念。類路徑是執行時將查詢以查詢依賴程式碼的路徑元素的列表。當執行Java類,定義通過類路徑中-cp的命令列選項:

java -cp lib/one.jar;lib/two.jar;/var/lib/three.jar path.to.MainClass

Java執行時通過聚合來自所有引用的JAR的所有類並新增主類來建立類路徑。

分發依賴於其他JAR的JAR時會出現新的問題:

  1. 您需要在相同版本中定義相同的庫
  2. 更重要的是,該-cp引數不適用於JARs。要引用其他JAR,需要在JAR清單中通過Class-Path屬性設定類路徑:Class-Path: lib/one.jar;lib/two.jar;/var/lib/three.jar
  3. 因此,您需要根據清單將JAR放在目標檔案系統上的相對或絕對*相同的位置。這意味著要開啟JAR並先閱讀清單。

 

Apache Assembly外掛

Maven的Assembly Plugin使開發人員能夠將專案輸出組合到一個可分發的存檔中,該存檔還包含依賴項,模組,站點文件和其他檔案。

Assembly外掛依賴於特定的assembly.xml配置檔案。它允許您選擇要包含在工件中的檔案。請注意,最終的工件不必是JAR:配置檔案可讓您在可用格式(例如zip,war等)之間進行選擇。

該外掛通過提供預定義的程式集來管理常見的用例。自包含的JAR的分佈在其中。配置如下pom.xml所示:

<plugin>
  <artifactId>maven-assembly-plugin</artifactId>
  <configuration>
    <descriptorRefs>
      <descriptorRef>jar-with-dependencies</descriptorRef>                            
    </descriptorRefs>
    <archive>
      <manifest>
        <mainClass>ch.frankel.blog.executablejar.ExecutableJarApplication</mainClass> 
      </manifest>
    </archive>
  </configuration>
  <executions>
    <execution>
      <goals>
        <goal>single</goal>                                                           
      </goals>
      <phase>package</phase>                                                          
    </execution>
  </executions>
</plugin>

上述步驟:

  • 參考預定義的自包含JAR配置
  • 設定要執行的主類
  • 執行single目標
  • 將目標繫結到package階段,即在構建原始JAR之後

執行mvn package產生兩個工件:

  1. <name>-<version>.jar
  2. <name>-<version>-with-dependencies.jar

第一個JAR的內容與沒有該外掛時建立的內容相同。第二個是獨立的JAR。您可以像這樣執行它:

java -jar target/executable-jar-0.0.1-SNAPSHOT.jar

根據專案的不同,它可能會成功執行... 例如,它在示例Spring Boot專案中失敗,並顯示以下訊息:

%d [%thread] %-5level %logger - %msg%n java.lang.IllegalArgumentException:
  No auto configuration classes found in META-INF/spring.factories.
  If you are using a custom packaging, make sure that file is correct.
%d [%thread]%-5level%logger-%msg%n java.lang.IllegalArgumentException:
  在META-INF/spring.factories中找不到自動配置類。
  如果使用的是自定義包裝,請確保該檔案正確無誤。

原因是在同一路徑下不同的JAR提供不同的資原始檔,例如 META-INF/spring.factories。該外掛遵循最後寫入勝出策略覆蓋相同資原始檔。順序是基於JAR的名稱。

使用Assembly,您可以排除資源,但不能合併它們。當您需要合併資源時,您可能需要使用Apache Shade外掛。

 

Apache Shade外掛

Assembly外掛是通用的;Shade外掛僅專注於建立獨立的JAR的任務。

該外掛提供了將工件打包(包括其依賴項)並遮蔽(即重新命名)某些依賴項的包的功能。

該外掛基於轉換器的概念:每個轉換器負責處理一種型別的資源。轉換器可以按原樣複製資源,新增靜態內容,將其與其他資源合併等。

雖然您可以開發一個轉換器,但是該外掛提供了一組現成的轉換器:

ApacheLicenseResourceTransformer:防止許可證重複
ApacheNoticeResourceTransformer:準備合併 NOTICE
AppendingTransformer:向資源新增內容
ComponentsXmlResourceTransformer:聚合多個 components.xml
DontIncludeResourceTransformer:防止包含匹配資源
GroovyResourceTransformer:合併Apache Groovy擴充套件模組
IncludeResourceTransformer:從專案新增檔案
ManifestResourceTransformer:在 MANIFEST設定輸入項
PluginXmlResourceTransformer:聚合多個Maven配置 plugin.xml
ResourceBundleAppendingTransformer:合併ResourceBundle
ServicesResourceTransformer:META-INF/services資源中已重定位的類名並將其合併
XmlAppendingTransformer:將XML內容新增到XML資源
PropertiesTransformer:合併擁有序數的屬性檔案以解決衝突
OpenWebBeansPropertiesTransformer:合併Apache OpenWebBeans配置檔案
MicroprofileConfigTransformer:根據序號合併衝突的Microprofile配置屬性

 

上面程式集的Shade外掛配置如下:

<plugin>
  <artifactId>maven-shade-plugin</artifactId>
  <executions>
    <execution>
      <id>shade</id>
      <goals>
        <goal>shade</goal>                        
      </goals>
      <configuration>
        <transformers>
          <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> 
            <mainClass>ch.frankel.blog.executablejar.ExecutableJarApplication</mainClass> 
            <manifestEntries>
              <Multi-Release>true</Multi-Release> 
            </manifestEntries>
          </transformer>
        </transformers>
      </configuration>
    </execution>
  </executions>
</plugin>

上述配置說明:

  • 預設情況下,shade目標已繫結到package階段
  • 該轉換器專用於生成清單檔案
  • 設定Main-Class條目
  • 將最終的JAR配置為多發行版JAR。當任何初始JAR是多發行版JAR時,這都是必需的

執行mvn package產生兩個工件:

  1. <name>-<version>.jar:自包含的可執行檔案JAR
  2. original-<name>-<version>.jar:沒有嵌入式依賴項的“普通”

對於示例專案,最終的可執行檔案仍然無法按預期工作。確實,在構建期間有很多關於重複資源的警告。其中兩個阻止示例專案正常工作。為了正確地合併它們,我們需要看一下它們的格式:

  • META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat:此Log4J2檔案包含預編譯的Log4J2外掛資料。它以二進位制格式編碼,任何開箱即用的轉換器都無法合併此類檔案。然而,隨便搜尋發現有人已經遇到了這個問題,併發布了一個轉換器來處理合並。
  • META-INF/spring.factories:這些特定於Spring的檔案具有單鍵/多值格式。雖然它們是基於文字的,但沒有開箱即用的轉換器可以正確合併它們。但是,Spring開發人員在其外掛中提供了此功能(以及更多)。

要配置這些轉換器,我們需要將以上庫作為依賴項新增到Shade外掛中:

<plugin>
  <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">
            <mainClass>ch.frankel.blog.executablejar.ExecutableJarApplication</mainClass>
            <manifestEntries>
              <Multi-Release>true</Multi-Release>
            </manifestEntries>
          </transformer>
          <transformer implementation="com.github.edwgiz.maven_shade_plugin.log4j2_cache_transformer.PluginsCacheFileTransformer" /> 
          <transformer implementation="org.springframework.boot.maven.PropertiesMergingResourceTransformer"> 
            <resource>META-INF/spring.factories</resource>
          </transformer>
        </transformers>
      </configuration>
    </execution>
  </executions>
  <dependencies>
    <dependency>
      <groupId>com.github.edwgiz</groupId>
      <artifactId>maven-shade-plugin.log4j2-cachefile-transformer</artifactId> 
      <version>2.14.0</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>                        
      <version>2.4.1</version>
    </dependency>
  </dependencies>
</plugin>

上述配置說明:

  • 合併Log4J2.dat檔案
  • 合併/META-INF/spring.factories檔案
  • 新增所需的變壓器程式碼

此配置有效!仍然有剩餘警告:

  • Manifests
  • Licenses, notices and similar files
  • Spring Boot specific files i.e. spring.handlers, spring.schemas and spring.tooling
  • Spring Boot-Kotlin specific files e.g. spring-boot.kotlin_module, spring-context.kotlin_module, etc.
  • Service loader configuration files
  • JSON files

您可以新增和配置其他變壓器來修復其餘的警告。總而言之,整個過程需要對每種資源以及如何使用它們有深刻的理解。

 

Spring Boot外掛

Spring Boot外掛採用了完全不同的方法。它不會單獨合併來自JAR的資源。它增加了相關的JAR ,因為它們是 uber  JAR。為了載入類和資源,它提供了一種特定的類載入機制。顯然,它專用於Spring Boot專案。

配置Spring Boot外掛很簡單:

<plugin>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-maven-plugin</artifactId>
  <version>2.4.1</version>
  <executions>
    <execution>
      <goals>
        <goal>repackage</goal>
      </goals>
    </execution>
  </executions>
</plugin>

讓我們檢查一下最終JAR的結構:

/
 |__ BOOT-INF
 |    |__ classes           
 |    |__ lib               
 |__ META-INF
 |    |__ MANIFEST.MF
 |__ org
      |__ springframework
           |__ loader       

目錄說明:

  • classes:專案編譯類
  • lib:JAR依賴
  • loader:Spring Boot類載入類

這是我們的示例專案清單檔案配置:

Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: ch.frankel.blog.executablejar.ExecutableJarApplication

如您所見,主類是特定於Spring Boot的類,而“真實”主類在另一個條目下被引用。

有關JAR結構的更多資訊,請參閱參考文件

 

結論

在這篇文章中,我們描述了三種建立獨立的可執行JAR的方法:

  1. 組裝非常適合簡單的專案
  2. 當專案開始變得更加複雜並且您需要處理重複的檔案時,請使用Shade
  3. 最後,對於Spring Boot專案,最好的選擇是專用外掛

可以在Github上以Maven格式找到此帖子的完整原始碼。

 

相關文章