torutkのブログ

ソフトウェア・エンジニアのブログ

JavaFXアプリケーションのJDK 11対応(NetBeans編)(モジュール対応編)

はじめに

じゃばえふえっくす Advent Calendar 2018 - Qiita20日目のエントリです。

前回のブログ(JavaFXアプリケーションのJDK 11対応(NetBeans編) - torutkのブログ)では、アプリケーションをJava SE 9から導入されたモジュールシステム(Java Platform Module System、以下JPMSと呼ぶ)には対応せずに、JavaFXライブラリをクラスパスで参照する方法でJDK 11に対応させました。

しかしながら、JPMS対応をしなかったため、実行時に--add-modulesを指定するか、メインクラス(javaランチャーから最初に指定するクラス)のロード時にJavaFXのクラスを参照しないようにする(javafx.application.Applicationの継承をやめる)必要があります。

そこで、今回のエントリではJPMS対応によるJDK 11対応をすることにします。

JPMS対応することで、上述の制約がなくなります。さらに、Java実行環境と一緒にアプリケーションを配布する際に、Java実行環境をアプリケーションに必要なモジュールに限定することで配布サイズ削減が図れます。jlinkコマンドでアプリケーションとアプリケーション実行に必要とするモジュールだけを1か所にまとめた実行イメージを作成できます*1

対応方法

プロジェクトの作成

NetBeans 10で[File]メニュー > [New Project...] で、カテゴリ[Java]から[Java Modular Project]を選択します。

f:id:torutk:20181209000836p:plain
NetBeans 10 New Project Java Modular Project

プロジェクト名とプロジェクトを作成するフォルダを指定します。

f:id:torutk:20181209001657p:plain
NetBeans 10 New Project Java Modular Project で プロジェクト名と場所を指定

Java Modular Projectで生成したプロジェクトでは、プロジェクトの下に複数のモジュールが定義できるマルチモジュールな構成となります。よって、プロジェクト作成後に、少なくとも1つのモジュールを定義します。

プロジェクトペインで今作成したプロジェクトを右クリックし、[New] > [Module]を選択します。

f:id:torutk:20181209002117p:plain
NetBeans 10 Java Modular Project に新しいモジュールを指定

モジュール名を指定します。モジュール名は、一般的にはモジュールに含むパッケージ名のうち代表的なものとします。

f:id:torutk:20181209002535p:plain
NetBeans 10 Project に新しいモジュールを指定

モジュールを作成すると、プロジェクトの配下にモジュールが追加され、その中にmodule-info.javaが生成されます。

f:id:torutk:20181209002845p:plain
NetBeans 10 Project に追加された新しいモジュール

JavaFXモジュールのパスを指定

プロジェクトのプロパティを開き、左側ペインで[Libraries]を選択、右側ペインの[Compile]タブで、Modulepath の右端の[+]をクリックします。ポップアップメニューが出るので、[Add Library」を選択、ライブラリ一覧からJavaFX 11を選択します(NetBeansのライブラリにJavaFX 11を追加する方法は、JavaFXのNetBeans設定 - ソフトウェアエンジニアリング - Torutk参照)。

f:id:torutk:20181209004431p:plain
NetBeans 10 Project のモジュールパスにJavaFX 11を指定

ソースファイルの移動

NetBeans 8.2/JDK 8で作成したJavaFXアプリケーションのソースファイルを、新しく作成した Java Modular Projectのプロジェクトへ移動します。NetBeans 10で旧プロジェクトを開き、ソースファイルを含むパッケージをマウスでドラッグし、新プロジェクトのモジュールの下の[classes]にドロップします。

f:id:torutk:20181219072428p:plain
旧プロジェクトから新プロジェクトへのソースファイル移動

そのままドロップするとフォルダが移動になるので、コピーするにはCtrlキーを押しながらドロップします。

module-info.javaの定義

プロジェクトのライブラリ定義のモジュールパスにJavaFX 11ライブラリを指定しただけでは、アプリケーションからJavaFXのクラスを参照することはできません。JavaFXライブラリのクラスを参照するimport文がエラーになってしまいます。

f:id:torutk:20181209005220p:plain
NetBeans 10 Project のモジュールパスにJavaFX 11を指定しただけでは参照を解決できず

そこで、プロジェクトのモジュールパスにJavaFXライブラリを指定した後、module-info.javaに使いたいクラスが含まれるモジュールを定義します。

module com.torutk.gadget.image {
    requires javafx.controls;
    requires javafx.fxml;
    opens com.torutk.gadget.image to javafx.graphics, javafx.fxml;
}
  • 基本はjavafx.controls モジュールへの依存を記述。javafx.controlsからjavafx.graphicsモジュールへの依存の推移により暗黙的な依存が定義されるので、javafx.graphicsへの依存は記述不要。
  • FXMLを使う場合は、javafx.fxmlモジュールへの依存を記述。
  • アプリケーションのメインクラス(javafx.application.Applicationクラスを継承するアプリケーションクラス)を持つパッケージ名をopensで記述。

ビルド成果物

プロジェクトの下に定義したモジュール毎に、ビルド成果物であるモジュールファイル(JARファイル)が生成されます。

f:id:torutk:20181220061707p:plain
ビルド成果物

JARファイルの内容が次です。モジュール記述子(module-info.class)が含まれています。

f:id:torutk:20181220071325p:plain
モジュールファイル(JAR)の内容

コマンドラインからの実行

NetBeans上でビルドしたJARファイルをコマンドラインから実行するには、少々長いオプションを指定します。

D:\work\ImageGadget> java --module-path "C:\Program Files\Java\JavaFX\javafx-sdk-11.0.1\lib";dist -m com.torutk.gadget.image/com.torutk.gadget.image.ImageGadgetApp
  • JavaFX 11ライブラリは、C:\Program Files\Java\JavaFX\javafx-sdk-11.0.1 にインストール
  • アプリケーションのモジュールJARファイルは、カレントディレクトリのdist下にある

NetBeans上でメインクラスの指定をJARマニフェストにする方法が見つからなかったのでオプションが長くなってしまいました。

まとめ

  • アプリケーションをJPMS対応するには、NetBeansのプロジェクト種類でJava Modular Projectを選ぶ
  • Java Modular Projectで生成したプロジェクトでは、最低1つはモジュールを作成する
  • JDK標準以外のライブラリを利用するときは、プロジェクトのプロパティでライブラリをモジュールパスに登録する(クラスパスではなく)
  • java.baseモジュールに含まれるクラス以外を利用するときは、モジュール定義(module-info.java)に依存を記述する

*1:@skrbさんのコメントをもとに文章を修正しました。モジュール対応・非モジュール対応のアプリケーションの実行イメージの作成については、 JavaFXアプリケーションのJDK 11対応(配布編) - torutkのブログ に書きました。

JavaFXアプリケーションのJDK 11対応(NetBeans編)

はじめに

これまでにNetBeans 8.2/JDK 8のAntプロジェクト(種類はJavaFXアプリケーション)で作成したJavaFXアプリケーションを、JDK 11に対応させようと試みました。

NetBeans は間もなくリリース予定のNetBeans 10のテストバージョン(vc4)で、OpenJDK 11.0.1とJavaFX 11.0.1を使います。

 今回はアプリケーション側のモジュール対応は行いません。移行後に段階的にモジュール対応を進めます。

最初の試み

まずは、JDK 11からJavaFXが分離したのに伴い、JavaFX 11を別途インストールしマシン上に展開、NetBeansのプロジェクト設定でライブラリとしてこのJavaFXを指定する方針で進めました。

 

JavaFX 11.0.1をダウンロードし、NetBeansのライブラリ定義に加え、既存のJavaFXアプリケーションのプロジェクトにクラスパスとして追加します。

コンパイルは通りますが、実行すると

JavaFX deployment library not found in active JDK.
Please check that the JDK is correctly installed and its version is at least 7u4 on Mac or 7u6 on other systems.

とエラーになってしまいます。NetBeansのビルド定義には、JDKの中にあるJavaFXライブラリの場所を探すコードがあり、見つからないと上述のようにエラーとなってしまいます。

この問題を解決するには、NetBeansが生成したビルド定義に大分手を入れる必要があります。この試みは挫折とします。

次の試み

次は、NetBeansでプロジェクトを新規に作り直す方法で進めます。

既存のディレクトリにNetBeansで新規プロジェクトを作成するとエラーになるので、新しいディレクトリにプロジェクトを作成します。

プロジェクト種類をJavaFXアプリケーションで新規作成しようとするとエラー

このとき、プロジェクトの種類をJavaFXアプリケーションとすると、

Failed to automatically set-up a JavaFX Platform.
Please go to Platform Manager, create a non-default Java SE platform, then go to the JavaFX tab,
enable JavaFX and fill in the paths to valid JavaFX SDK and JavaFX Runtime.

とエラーになります。

プロジェクト種類をJavaアプリケーションで新規作成するとビルドはOKだが実行エラー

そこで、プロジェクト種類をJavaアプリケーションとして新規作成します。そこに既存のソースコードを追加し、ライブラリにJavaFX 11を追加します。

これでビルドは成功しますが、アプリケーションを実行するとエラーとなってしまいます。

エラー: JavaFXランタイム・コンポーネントが不足しており、このアプリケーションの実行に必要です

--add-modulesでJavaFXモジュールを指定して実行エラー回避

解決方法は、javaの実行時のJVMオプションで、--add-modules=javafx.controls のようにアプリケーションから利用するJavaFXのモジュールを指定します。FXMLを使うアプリケーションの場合は、--add-modules=javafx.controls,javafx.fxml のように指定します。

Getting Started with JavaFX 11

なぜ--add-modulesでJavaFXモジュールを指定しなければならないか

ですが、なぜJavaFXのライブラリ(JARファイル)をクラスパスで指定しているのにエラーとなってしまうのか不思議でした。

いろいろ調べてみたところ、次のメーリングリストでその原因が言及されていました。

launching JavaFX in 11

これによると、実行時のエラーはjavaコマンドが最初に参照するメインクラスがJavaFXのApplicationクラスを継承している場合に発生します。javaコマンドがメインクラスをロードする際に、メインクラスが継承しているスーパークラスもロードする必要があります。しかしJavaFXのApplicationクラスがスーパークラスの場合、javafx.graphicsモジュールを探しに行って存在しない場合、クラスパスを探すことなくエラーとなります。

Mainクラスでjavafx.application.Applicationクラスの継承をやめる

試しに、メインクラスでJavaFXのApplicationクラスを継承するのをやめて、mainメソッドの中でJavaFXのApplicationを継承する別クラスを初期化するコードを呼ぶように修正したところ、--add-modulesの指定をせずに実行できるようになりました。

まとめ

  •  NetBeansJavaFXアプリケーション・プロジェクトは、JavaFXが分離したJDKOracle JDK 11以降およびOpenJDK)には対応できない
  • NetBeansJavaアプリケーション・プロジェクトでJavaFXアプリケーションをビルドする際は、JavaFX 11を別途ダウンロードしマシン上に配置し、NetBeansのライブラリ定義を追加し、プロジェクトからJavaFXライブラリを参照する(クラスパスとして参照)
  • NetBeansJavaアプリケーション・プロジェクトでJavaFXアプリケーションを実行する際は、メインクラスにJavaFXのApplicationクラスを継承させず、別クラスで継承し、メインクラスのmainメソッドにはその別クラスを初期化するコードを記述する。

 

JDK 11の環境設定

JDK 11から、Oracleが提供するWindows OS向けのJDKは、Oracle JDKとOpenJDKの2種類になりました。

Oracle JDK 11は、有償で技術サポートが含まれ、LTS(長期サポート版)となる商用製品となりました。なお、開発・試験・デモ用など使用用途に制限のあるOTNライセンスでの無償提供もあります。
一方、OpenJDK 11は、無償で提供されています。

Oracle JDKもそのソースコードはOpenJDKとして開発されています。従来はOpenJDKのソースには含まれないOracle JDK独自機能(Java Flight Recorder、Application Class Data Sharing、フォント描画等)がいくつかありましたが、JDK 11に向けてOpenJDKに統合されてきています。このOpenJDKのソースはGPLであり、OpenJDKのソースをビルドしてバイナリを提供する団体がいくつか登場しています。

JDKの入手先

Oracle JDK 11

商用利用するアプリケーションをこのOracle JDKで動かす場合には、有償のライセンス契約が必要となります。開発・試験・デモ用など使用用途に制限のあるOTNライセンスは無償で提供されています。

Java SE - Downloads | Oracle Technology Network | Oracle

OpenJDK 11 Oracleビルド版

先のOracle JDK 11とは別に、OracleがOpenJDKソースをビルドしてバイナリを提供しています。
GPLv2 のクラスパス例外付きライセンスで提供されています(無償)。

JDK 11.0.1 GA Release

Zulu

Azul Systems社がOpenJDKを独自にビルドしバイナリを提供しています。Zuluの利用は無償で、技術サポートを有償で提供しています。

Download OpenJDK Java Linux Windows macOS Alpine Java 11 Java 8

AdoptOpenJDK

ロンドンJavaコミュニティが運営し、OpenJDKコミュニティの幅広いメンバーの活動によってOpenJDKをビルド・提供する団体です。

AdoptOpenJDK - Open source, prebuilt OpenJDK binaries

Windows上でのOracle JDKの環境設定バッチ

Oracle JDKインストーラーでインストールし、レジストリにインストール場所が記録されます。これまでは、レジストリからJDKの場所を調べて環境変数を設定するバッチファイルを作って環境設定をしていました。

Setting oracle jdk path using registory for Windows command prompt - Gist

OpenJDKは、zipで提供、インストールしたい場所に展開するだけのインストールなので、レジストリからOpenJDKの場所を調べることができません。そこで、自分で決めたインストールディレクトリの下にJDK-<バージョン番号>のディレクトリを探してそれを環境変数に設定するようにしました。

LTSについて

これまで、JDKは1年半〜4年半の間隔でメジャーバージョンアップをしてきました。また、マイナーバージョンアップにおいても、セキュリティアップデート、バグ修正のほか機能追加が行われたりしていました。

これからは、LTS版はセキュリティアップデートとバグ修正が続き、機能追加は半年毎にバージョンアップされるOpenJDKの方で行われるようになります。なので、JDK 12、13、14、15、16と半年毎にリリースされている間、LTS版のJDK 11はセキュリティパッチとバグ修正パッチが順次提供されます。

なので、ミッションクリティカルなシステムではこのLTS版を使うと、より頑健になります。

ミッションクリティカルでなく、これまで3ヶ月毎にリリースされるCPU(Critical Patch Updte)やPSU(Patch Set Update)のJDKに更新していない、リリース時点のJDKバージョンで固定して使っているシステムでは、OpenJDKで十分でしょう*1

また、LinuxディストリビューションにはこれまでもOpenJDKが含まれていたので、こちらを使っていたシステムはそのままLinuxディストリビューションのOpenJDKを使い続けることになると思います。

*1:3ヶ月毎にJDKをアップデートして回帰テストなんてやってられないや、というシステムなど

Wix toolset 3.11のインストールで.NET 3.5を要求される

自宅PCのインストールソフトウェアを整理していて、Wix toolset(ちょっと古いバージョン)をアンインストールして最新の3.11.1をインストールしようとしたところ、.NET Framework 3.5が必要とインストーラーがエラー停止しました。
Windows 10では、.NET Framework 4.6(Creator Updateで4.7)が入っていますが、これではダメで古い .NET Framework 3.5がないと先に進めません。

コントロールパネルの[プログラム]>[プログラムと機能]>[Windowsの機能の有効化または無効化]から.NET Framework 3.5にチェックを付けてインストールする手順で対処可能なようです。

Wix 3.14(3系の次のリリース予定)またはWix 4において、.NET Framework 3.5への依存が解消されるそうですが、まだどちらもリリースはされていません。

インターネット非接続環境での.NET 3.5のインストール

Windows 10にインターネット非接続な環境で.NET Framework 3.5をインストールする方法を調べてみたら、Windows 10のインストールメディアを必要とします。

.NET Framework 3.5 を有効化する手順について ( Windows 10 ) | Ask CORE

うーん、プレインストールのノートPCではWindows 10のインストールメディアはないし、インターネット接続環境した別マシンでWindows 10のインストールメディアを作成し、外付けドライブに置いてそれを持ってくるとかとっても面倒です。

なお、アプリケーション実行用のランタイムイメージは.NET Framework 3.5については用意されていないようですね。ということは、.NET Framework 3.5以下で作成したアプリをWindows 10で使うのはかなり難関(不可能ではないが)です。

はてな日記のサービス終了がそろそろ

夏頃に、はてな日記のサービスが終了になるとのアナウンスがありました。終了予定は2019年春頃です。はてなブログに移行するか、他のブログサービスに乗り換えるかの選択となります。

2019年春「はてなダイアリー」終了のお知らせと「はてなブログ」への移行のお願い - はてなダイアリー日記

はてなブログへ移行する場合は、はてな日記上のコンテンツのURLは移行先のはてなブログへリダイレクトされるので、別サイトからリンクを張っていた場合もリンク切れになることはなさそうです。

はてな日記をいつ頃使い始めたのかを遡ってみると、2004年9月2日が最初のはてな日記書き込みでした。今から14年前ですね。日帰り出張(たしか岐阜へ)で早朝〜午前様となった愚痴とともに往復の電車でリーンソフトウェア開発本を読んだ一日だったらしいです。

出張、疲れた - torutkの日記

これまで、1113日分を書いているので*1、1年あたりでは80日と一週間に1〜2日のペースで書いていたことになります。

*1:プロフィールページに書き込んだ日数が表示されています

Java読書会BOF主催「現場で役立つシステム設計」を読む会(第1回)の感想 #javareading

Java読書会BOF主催「現場で役立つシステム設計」を読む会が今月から始まります #javareading - torutkのブログで案内したJava読書会「現場で役立つシステム設計」を読む会(第1回)が8月18日(土)に実施されました。
初回は、「はじめに」から、「CHAPTER 4 ドメインモデルの考え方で設計する」の導入部分(p.102上4行目)まで90ページ弱を読み進めました。

http://www.javareading.com/bof/

議事録は近日Java読書会BOFのWebサイトにアップされるとので、自分の感想をずらずらと記載していきます。

書籍の題名について

書籍の題名「現場で役立つシステム設計の原則」は、最初に題名だけ見たときに「システム設計」とあることから、ソフトウェア設計の前段にあるシステム設計(業務の設計、計算機・ネットワーク等のインフラとソフトウェアの配分)を意図したものと誤解していました。
しかしJava読書会BOFの課題図書投票で推薦本に挙がり、内容を見てみたところソフトウェア設計、しかもオブジェクト指向設計な内容だったので、これはと思いました。(昨年投票時は次点でしたが、今回は1位となって読書会をする運びとなりました)

CHAPTER 1 小さくまとめてわかりやすくする

ここはプログラマーとして苦労する問題を共有(共感)し、ドメインモデルへの導入とするためでしょうか、プログラマーに寄り添った内容となっている、いい導入だなと感じました。

変数名

今まで関与したコーディング規約には、命名は原則フルスペルで、略語を使う場合は略語集(用語集)に登録した上で使うとしてきたので、素直に賛同です。

IDEやプログラミング用のエディタであれば、名前の補完があるので長い変数名でも最初の数文字を入れればあとは補完してくれるので入力の手間もそれほどはないと思います。

いまでも変数名等の名前を省略するコードがあるのか参加者に聞いてみましたが、昔の(10年以上前とか)コードで見かけるが、今書かれているものは省略するものは少ないようでした。
一方で、真面目に業務(問題領域)の用語を英語にすると30文字以上になることもあるといった長すぎる名前をどうするか課題もあるとのことでした。

業務の用語については、対応する英語名を一意に決めないとプログラマーによって異なる英語(類語)が使われるので、プロジェクトで用語集(日英対応まで含めて)を定めるのが重要です。ただしプログラミングが始まってから後追いで用語集を策定しても手戻りが増えて大変(反発も大きい)です。

空白行

長いメソッドは段落(意味的に一続きのコード)ごとに空行を入れて読みやすくするという手法は自分でも実践しているので素直にうなずけます。

ただ、この書籍のサンプルコードは、if文の条件式を囲う丸括弧の内側に空白文字があるところ、ifのキーワードと条件式の丸括弧開きの間に空白がないといったところで慣習的なスタイルと違いがあってそっちが気になってしまいました…。

説明用の変数とメソッドとしての独立

Java読書会でも以前読んだ「リファクタリング」の内容でもあり、違和感なくうなずけます。

値の範囲を制限してプログラムを分かりやすく安全にする

制御系のアプリケーションでは、範囲を定義して範囲外の値を代入できないようにすることが必要でした。必要性はとってもうなずけます。

QuantityやUnitといえば、JavaAPIとしてJSR 363が作られています。JSR 363の簡単な紹介を次に書いています。2018年に入ってJSR 385がAPIのVer.2.0として了承されたようです。
JSR 363 Units of Measurement を調べてみて

範囲を扱うRangeクラスもいくつかのライブラリで使われています。
id:torutk:20110924

値オブジェクトは「不変」にする

この本では言及はありませんが、マルチスレッドプログラミングでスレッド間でオブジェクトを安全に受け渡しする際には「不変」が重要となります。

コレクション型を扱うロジックを専用クラスに閉じ込める

ファーストクラスコレクションというそうですが、これは意識したことがないので有用かどうかは試してみたいところです。

ジェネリックスがJavaに導入される前はコレクションに入れる型を保証するためにコレクションを内部に持ち外部からコレクションへ追加するメソッドで型を規定したことはありました。これの発展形なのかなぁと漠然と思いました。

CHAPTER 2 場合分けのロジックを整理する

判断や処理のロジックをメソッドに独立させる

if文の条件式が複雑になるときは、条件式をメソッド化して整理するのはよく使う手法なので、うなずけます。

else句をなくすと条件分岐が簡単になる

節題がドキッとする内容(elseをなくすとロジックの抜け漏れが発生するんでは?)ですが、読み進めてみるとガード節(早期リターン)の導入でした。これもよく使う手法です。

Javaの列挙型を使えばもっとかんたん

Javaenumいいです。ぜひ活用しましょう。

状態の遷移ルールをわかりやすく記述する

enum導入以前に状態遷移ロジック書いたきりなので、enumで状態遷移ロジックというのは実装したこと、実装しようと思ったことがなかったです。ちょっと目から鱗です。

サンプルコードで、EnumSetを使っていながら、EnumMap使わないでHashMapつかったのは何か意図があるのかなと議論になりました。

CHAPTER 3 業務ロジックを分かりやすく整理する

いろいろな見方があって議論が高まる章となりました。
Java読書会BOFのモットー(自称)は、「脳みそに汗をかこう」*1なので、読書会の本としては成功です。

共通機能ライブラリが失敗する理由

Utilクラス、Commonクラスアプローチではうまくいかないなということは身をもって痛感し、自分ならNGワードとしたい命名です。Commonといいつつ大抵は狭い範囲での共通に過ぎないものばかりでした。

一方で、「データクラスと機能クラスに分ける設計」としては、C++STLライブラリなどがあります。Java 8ではStream APIラムダ式とともに導入されています。

ちょっとしたニーズの違いについては、フラグやオプション引数で対応するのではなく、ラムダ式で違いの部分をコードで利用側から指定するなどの方法もあります。言語仕様、標準APIの進歩でこれまで難しかったことも状況が変わってきています。

このあとまだまだ興味深い内容がつづきますが、日記の方は続き、ということで。