虎の穴開発室ブログ

虎の穴ラボ株式会社所属のエンジニアが書く技術ブログです

MENU

【Java】Windows 向けJRE同梱アプリケーションを作る

こんにちは。虎の穴ラボのS.Aです。

今回はJavaで作成したアプリケーションをWindows向けに実行可能な状態で配布する方法についてご紹介します。 近年アプリの主流は、WEBアプリになり、デスクトップアプリを作成する機会は減ってきているとは思いますが、 リリースサイクルの変化で急速に変化するJavaの世界で、デスクトップアプリのビルド方法も様々に変化しているため、 備忘を兼ねて記事にまとめようと思います。

jarファイルを作る

Javaで実行可能な形式と言えば、jarファイルがあります。 jarファイルの作成方法はいくつかありますが、Gradle であれば java plugin の jar タスクが使えます。

例えば次のようなサンプルアプリケーションがあるとします。 (Hello World と書かれたウィンドウが出るだけのアプリケーション)

package jp.example;
import javax.swing.*;
import java.awt.*;

public class Application {
    private Application(){
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);
        frame.getContentPane().setPreferredSize(new Dimension(300, 300));
        JLabel label = new JLabel("Hello world.");
        frame.add(label);
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(Application::new);
    }
}

build.gradle で java プラグインを追加し、 jar タスクの設定として Main-Class に先ほどのクラスを指定します。 jar タスクを実行すれば、./build/lib 配下に jar が作成されます。

apply plugin: 'java'

jar {
    manifest {
        attributes 'Main-Class': 'jp.example.Application'
    }
}

jarの実行結果はこんな感じです。

開発者の環境では、JDKがインストールされているため jarファイルをそのまま実行できますが、 一般ユーザに広くアプリケーションを配布する場合は、jarファイルのままだと不自由だったりします。 Windows 環境で動かすとして、例えば 次のような問題に直面します。

・jar が関連付けされていないためクリックで実行できない (exeにする必要がある)

・実行環境(JRE)がないため動かない

・インストールされているJavaのバージョンが違うため動かない

開発者であれば難なく対処できるかもしれませんが、 実行に必要なファイルが全て同梱されていて、何も考えずクリックだけですぐに実行できるような形が理想です。

Javaのバージョンの話

同じJavaといえど、バージョンによって対処方法が違ってくるため、Javaのバージョンがどうなっているか少し書いておきます。 2023年2月現在、Java SEのバージョンは 19が最新としてリリースされています。 ただし、Java 19 や 18 などは、半年後にリリースされる新バージョンの登場を持ってサポート終了となるため、新規のプロジェクトではあまり選択されないバージョンとなっています。 対して、Java 8, 11, 17 などはLTS(長期サポート)版として、数年間サポートされるバージョンとなっています。

Java 8 の時代では、Javaのアプリケーションを実行するには、 ユーザ側が自身のPCにJRE(Java実行環境)をインストールしておく必要がありました。

しかし、Java 9 で導入された Project Jigsaw でモジュールを分けられるようになり、 JREの中からアプリケーション実行に必要最低限のライブラリを抽出できるようになると状況が変わります。

LTSである Java 11 からは、JRE が単独で配布されなくなりました。 これはつまり、Java 11 以降で作られたアプリでは、ユーザが自分のPCにJREをインストールするのではなく、 アプリ開発者が必要最小限のJREをアプリに同梱させ、ユーザ側はJREを個別にインストールすることなくアプリを実行できるような形に方向転換したということのようです。

Java 11 以降でJREを同梱する

では、Java 11以降で必要最小限のJREを同梱した Self-Contained Java Application を作るにはどのような手順が必要なのかというと、 ① jar を作る ② jdeps で依存モジュールを確認する ③ jlink で最小のJREを抽出する ④ launch4j などで exe を作る となります。

jdeps を使う

jarの作り方は前述の通りなので、②から確認します。 jdeps や jlink はJDKのインストールディレクトリ下の bin ディレクトリに存在するCLIのコマンドです。

jdeps --list-deps --ignore-missing-deps myapp-1.0.jar

上記のように jdeps コマンドに作成したjarファイル(ここでは myapp-1.0.jar)を指定すると、依存モジュールが列挙されて出力されます。

   java.base
   java.desktop

さきほど作ったサンプルアプリは Java標準の Swingライブラリでウィンドウを作っていたので、java.desktop などが出てきています。 サンプルアプリを動かすにはJREのモジュールのうち、 java.base と java.desktop だけが必要なので、この2つだけを抽出したJREを jlink で作ります。

jlink コマンドは --add-modules オプションの引数で必要モジュールをカンマ区切りで指定することで、必要最低限のJREを抽出してくれます。 また、--module-path オプションで、抽出元のモジュールの格納場所を指定する必要があります。 Windows向けにビルドする場合、Windows用のJREモジュールが必要になるので注意が必要です。 macで作業している場合、別途Windows用のJDKをzipでダウンロードして展開した後、jmodsディレクトリを指定する必要があります。

launch4j を使う

さて、これでjarと最小JREが作れたので、あとはexeにする作業になります。 これには launch4j というOSSを用います。 launch4j はGUIもありますが、Gradleのタスクも用意されているので、そちらを利用します。 build.gradle で launch4j の plugin を追加し、設定を記述すれば、あとは launch4j タスクを実行するだけで exe ファイルが出力されます。 launch4j の設定で重要なのは、 bundledJrePath オプションです。 ここでexeが参照するJREの場所を指定できるので、相対パスで先ほど作成した最小のJREのディレクトリを指定します。

plugins {
    id 'java'
    id 'edu.sc.seis.launch4j' version '2.5.3'
}

launch4j {
    mainClassName = 'jp.example.Application'
    outputDir = './product'
    bundledJrePath = './jre-min'
    bundledJre64Bit = true
}

一連の流れをGradle タスクにまとめる

以上で exe を作ることができましたが、 jdeps から始まる一連の流れを手作業で実施するのは骨が折れるので、Graldeタスクを作ってコマンド一つで exe を作成できるようにします。 また、exe と共に最小JREもまとめてアプリを配布する必要があるので、全体をzipにアーカイブするのが良いです。 これもGradle タスク内で整備しておきます。

jdeps と jlink は以下のようにシェルスクリプトにまとめて使うことにしました。

#!/bin/sh
JAR_PATH=build/libs/myapp-1.0.jar #Jarファイルのディレクトリ
OUT_PATH=build/product #JRE出力先ディレクトリ
WIN_JAVA_DIR=~/Downloads/jdk #Windows用のJDKのディレクトリ

jdeps --list-deps --ignore-missing-deps $JAR_PATH > tmp.txt
DEP_LIST=""
while read LINE; do
  if [ -z "$DEP_LIST" ]; then
      DEP_LIST="$(echo $LINE)"
  else
      DEP_LIST="$DEP_LIST,$(echo $LINE)"
  fi
done < tmp.txt
rm tmp.txt

JRE_DEST=$OUT_PATH/jre-min
if [ -e "$JRE_DEST" ]; then
  rm -rf $JRE_DEST
fi

jlink --compress=2 --module-path $WIN_JAVA_DIR/jmods --add-modules $DEP_LIST --output $JRE_DEST

あとはGradleで、jar作成->JRE作成/exe作成->zip作成 の流れを定義すればOKです。 まず createJREタスクを定義し、先ほどのシェルスクリプトを指定します。 jdeps は jarを参照するので、dependsOn に jar タスクを指定して jar作成後に実行させるようにします。

次に createZIPタスクを定義し、launch4j タスクと createJRE タスクを dependsOn に指定します。 これで、ZIPタスクを実行すれば、一連の流れが実行されるようになります。

task createJRE(type: Exec, dependsOn: ["jar"]) {
    workingDir '.'
    executable './create_jre.sh'
}

task createZIP(type: Zip, dependsOn: ["launch4j", "createJRE"]){
    archiveFileName = "product.zip"
    from 'build/product'
}

生成されたzip ファイルをwindows 環境で開くと、無事にアプリが実行されました。

ちなみに、今回はjdeps, jlink の流れをシェルスクリプトで実行しましたが、jlink の gralde プラグインもありました。

plugins{
    id 'org.beryx.jlink' version '2.21.0'
}

jlink {
    mainClass = 'jp.example.Application'
    options = ['--compress', '2']
    targetPlatform("win", "~/Downloads/jdk")
}

このプラグインを使う場合、あらかじめ依存モジュールは module-info.java に記述しておく必要があります。

module myapp.main{
    requires java.base;
    requires java.desktop;
}

Java 17 の jpackage

最後に、Java 17では、msiやexeのインストーラを作成できる jpackage が導入されているので、そちらを少しだけご紹介します。

jpackage を使うとJREを同梱したアプリケーションのインストーラを msi や exe dmg など任意の形式で作成できるようです。 アプリの jarファイルを作成してから、 jpackage コマンドを以下のように実行すれば exeファイルが出力されます。

jpackage --name MyApplication --input build/libs --main-jar myapp-1.0.jar --module-path ~/Downloads/jdk/jmods --add-modules java.base,java.desktop --win-shortcut --dest build/product

--module-path や --add-modules などのオプションは jlink と同じです。 --runtime-image オプションで最小JREを直接指定することもできるようです。

上記ではさらに、 --win-shortcut オプションで、windowsでの実行時にインストール後デスクトップショートカットができるような設定をつけています。

生成された exe を windows環境で実行したところ、アプリがインストールされ、デスクトップにショートカットが生成されました。 ショートカットを叩くことでアプリが起動しました。

先ほど補足で紹介した jlink の Gradle プラグインに jpackage タスクもあるため、そちらでも exe が生成できます。

あとがき

今回Java アプリケーションをWindows向けに実行可能な状態で配布するにはどうすればよいか整理して、 ひとえに Java といっても、バージョンによってどうアプリを配布すればよいか選択肢が変わってくることがわかりました。

特にJava 8 から Java 11 に移る中で、JREを最小化して同梱できるようになったのは大きな変化だと思います。 これからも新バージョンがリリースされていく中で、Javaでのデスクトップアプリ開発の最適解がどのように変化していくか注視していきたいと思います。

P.S.

採用

虎の穴では一緒に働く仲間を募集中です!
この記事を読んで、興味を持っていただけた方はぜひ弊社の採用情報をご覧下さい。
yumenosora.co.jp

LINEスタンプ

エンジニア専用のメイドちゃんスタンプが完成しました!
「あの場面」で思わず使いたくなるようなスタンプから、日常で役立つスタンプを合計40個用意しました。
エンジニアの皆さん、エンジニアでない方もぜひスタンプを確認してみてください。 store.line.me