使用Java 9的模塊化來構建零依賴的原生應用

  摘要:本文經過實例介紹了若是經過Java 9的模塊化特性來構建一個獨立的、零依賴的可執行程序。如下是譯文。java

  「爲何沒辦法建立一個.EXE程序?」git

  在Java剛剛出現的時候,主流的編程語言要麼能夠編譯爲獨立的可執行文件(例如C/C++、COBOL),要麼運行在解釋器中(例如Perl、Tcl)。對於大部分的程序員來講,Java對字節碼編譯器和運行時解釋器的需求讓他們開始轉變本身的思惟。編譯模型使得Java比「腳本」語言更適合於業務編程。然而,運行時模型則須要在每臺目標機器上部署和使用合適的JVM。程序員

  人們對此有些抵觸。在早期的網絡論壇,以及後來的StackOverflow問題中,爲了不在目標機器上安裝Java運行時,開發者們都在尋找某種能將Java應用程序發佈爲「原生」可執行文件的方法。github

  解決辦法從一開始就有。 Excelsior JET是一個超前的Java編譯器,提供了部分C++風格的體驗。然而,它的許可費用高達數千美圓。固然,也有免費工具,例如Launch4j和JDK 8的javapackager工具。這些工具能幫你把Java運行時環境與啓動程序捆綁在一塊兒,以使用該JRE來啓動應用程序。可是,嵌入JRE會使應用程序增長大約200MB。因爲技術上的緣由以及許可問題,應用程序的大小很難降下來。編程

  Java 9來了bash

  Java 9中最廣爲人知的新功能是新的模塊化系統,稱爲Jigsaw項目。簡而言之,這個新模塊用於隔離代碼塊及其依賴關係。網絡

  這個模塊不只適用於外部庫,也適用於Java標準庫自己。這意味着應用程序能夠聲明本身須要標準庫的哪些部分,並排除全部其餘的部分。編程語言

  這個功能是經過隨JDK一同提供的 jlink工具來實現的。乍一看,jlink相似於javapackager。它會生成一個包,包括:模塊化

  應用程序代碼及其依賴關係;工具

  一個嵌入式Java運行時環境;

  本地啓動器(例如,bash腳本或Windows批處理文件),用於經過嵌入式JRE啓動應用程序。

  jlink創建了「連接時」這個新的可選階段,它處於編譯時和運行時之間,用於執行優化,例如刪除不可達的代碼。與捆綁整個標準庫的javapackager不一樣,jlink把精簡過的JRE和僅包含應用程序所需的那些模塊捆綁在一塊兒。

  示例

  jlink和老版本的javapackager之間的區別很是的大。爲了說明這一點,咱們來看一個示例項目:

  https://github.com/steve-perkins/jlink-demo

  (1)建立一個模塊化的項目

  這個代碼庫包含一個多項目的Gradle構建。 cli子目錄中是一個「Hello World」命令行程序,而gui是一個JavaFX桌面應用程序。請注意,對於這二者,經過在build.gradle文件中配置下面這一句來讓每一個項目與Java 9兼容:

  sourceCompatibility = 1.9

  1

  2

  下面將建立module-info.java文件,爲每一個項目設置模塊化。

  /cli/src/main/java/module-info.java:

  module cli {

  }

  /gui/src/main/java/module-info.java:

  module gui {

  requires javafx.graphics;

  requires javafx.controls;

  exports gui;

  }

  這裏的CLI應用程序只是對System.out.println()的調用,因此它只依賴於java.base模塊(這是隱式的,不須要聲明)。

  但並非全部的應用程序都使用JavaFX,因此這裏的GUI應用程序必須聲明它對javafx.graphics和javafx.controls模塊的依賴。並且,因爲JavaFX的工做方式不太同樣,因此底層庫須要訪問咱們的代碼。export gui這一行賦予了這個庫的可見性。

  Java開發人員(包括我本身)須要一些時間來感悟新的標準庫模塊以及它們包含的內容。 JDK包含的jdeps工具能夠幫忙用來解決這個問題。並且只要某個項目被設置爲模塊化,IntelliJ就能識別出丟失的聲明並協助開發人員自動完成它們。即便Eclipse和NetBeans沒有相似的支持,它們很快也會添加進去的。

  (2)構建一個可執行的JAR

  要使用jlink構建一個可部署的包,首先要將應用程序打包成一個可執行的JAR文件。若是項目依賴第三方庫,那麼須要使用「shaded」或「fat-JAR」插件來生成包含全部依賴關係的單個JAR。咱們這個例子只使用了標準庫,因此構建一個可執行的JAR是一件很是簡單的事情,只需讓Gradle的jar插件包含一個聲明可執行類的META-INF/MANIFEST.MF文件:

  jar {

  manifest {

  attributes'Main-Class': 'cli.Main'

  }

  }

 

  (3)運行jlink

  據我所知,Gradle尚未一個插件可以提供乾淨而且能夠無縫集成的jlink。因此,個人構建腳本使用Exec任務來運行該工具。命令行調用是這樣的:

  [JAVA_HOME]/bin/jlink --module-path libs:[JAVA_HOME]/jmods --add-modules cli --launcher cli=cli/cli.Main --output dist --strip-debug --compress 2 --no-header-files --no-man-pages

  --module-path標誌相似於CLASSPATH。它告訴工具應該在哪裏查找已編譯的模塊二進制文件(即JAR文件或新的JMOD格式文件)。在這裏,咱們告訴它尋找項目的libs子目錄(由於這是Gradle放置可執行JAR的地方)以及標準庫模塊的JDK目錄。

  --add-modules標誌用於聲明哪些模塊要添加到結果包中。咱們只須要聲明本身項目Java開發培訓的模塊便可(cli或gui),由於它所依賴的模塊將做爲傳遞依賴被引入。

  生成的包將包含/bin子目錄,用於執行應用程序的bash腳本或Windows批處理文件。 --launcher標誌容許你爲這個腳本指定一個名字,以及它應該調用哪一個Java類(這看起來有點多餘,由於這已經在可執行JAR中指定了)。在上面的命令中,咱們將建立一個名爲bin/cli的腳本,它將調用模塊cli中的cli.Main類。

  --output標誌直觀地指定了放置結果包的子目錄。在這裏,咱們使用一個名爲dist的目標目錄。

  最後,這些標誌--strip-debug,--compress 2,--no-header-files和--no-man-pages是我所作的一些優化,以減小產生的包的大小。

  在項目的根目錄下,這個Gradle命令創建並連接了兩個子項目:

  ./gradlew linkAll

  1

  2

  由此產生的可部署包能夠在如下位置找到:

  [PROJECT_ROOT]/cli/build/dist

  [PROJECT_ROOT]/gui/build/dist

  結果

  咱們來看看連接出來的CLI和GUI應用程序的大小,以及精簡的嵌入式JRE:

  應用 原始大小 用7-zip壓縮後的大小

  cli 21.7 MB 10.8 MB

  gui 45.8 MB 29.1 MB

  這是在Windows機器上,用64位的JRE打包打出來的結果(Linux包的大小有點大,但比例仍大體相同)。下面是一些注意事項:

  做爲比較,這個平臺上完整的JRE大小是203MB。

  用Go編寫的「Hello World」命令行程序編譯出來是2MB左右。 Hugo是用於發佈此博客的網站生成器,它是一個27.2MB大小的Go可執行文件。

  對於跨平臺的GUI開發,一個典型的Qt或GTK應用程序僅附帶大約15MB的Windows DLL。Electron快速開始例程會生成一個131 MB的程序。

  結論

  說句公道話,一個包含啓動腳本的應用程序包並不像「單單一個.EXE程序」那樣簡單。另外,因爲JIT編譯器的緣由,JRE在啓動時相對較爲緩慢。

  即使如此,Java仍然是一種可以生成大小與其餘編譯語言相媲美、獨立的、零依賴的應用程序的語言(而且比Electron等Web混合程序更爲優秀)。此外,Java 9 包含了一個實驗性的AOT編譯器,這也許能夠加速程序的啓動。儘管這個jaotc工具最初只適用於64位Linux平臺,但它很快就會擴展到其餘平臺。

  雖然Go在早期的雲基礎設施CLI工具中備受矚目(例如Docker,Kubernetes、Consul、Vault等),但Java正在成爲一個強有力的替代者,特別是對於具備構建Java經驗的項目組。對於跨平臺桌面GUI應用程序,我認爲JavaFX與Java 9模塊化的結合是如今最好的選擇。

相關文章
相關標籤/搜索