Java 9とJigsawプロジェクトの最初のステップ-パート1

おはよう、ハブル!



本「 Java。A New Generation of Development 」以来、私たちは「 Project Jigsaw 」という一般名で統一されたこの言語の長年発表された新機能の開発を追ってきました。 本日、11月24日の記事の翻訳を提供します。これにより、JigsawがJava 9で行われることを十分に確信できます。



Jigsawプロジェクトが開始されてから8年が経過しました。Jigsawプロジェクトのタスクは、Javaプラットフォームをモジュール化し、共通モジュールシステムの実装に集約することです。 JigsawはJava 9で最初に登場する予定です。 このリリースは、以前はJava 7とJava 8の両方で計画されていました。Jigsawの範囲も何度も変更されています。 JavaOne 2015カンファレンスのOracleプレナリーで多くの注目を集めたJigsawと、このトピックに関するいくつかのスピーチ一度に行われたため、Jigsawの準備がほぼ完了したと信じるあらゆる理由があります。 これはあなたにとって何を意味しますか? Jigsawプロジェクトとは何ですか?



これは、モジュールシステムを簡単に紹介し、多数のコード例を使用してJigsawの動作を実証したい2つの出版物の最初のものです。 最初の部分では、モジュールのシステムとは何か、JDKがどのようにモジュール化されているかを議論し、特定の状況でのコンパイラとランタイムの動作についても検討します。



モジュールとは何ですか?



モジュールを記述するのは簡単です。これはプログラム内のユニットであり、各モジュールには3つの質問に対する回答がすぐに含まれています。 これらの回答はmodule-info.java





ファイルに記録されますmodule-info.java





module-info.java





各モジュールが持っています。











シンプルなモジュール



最初の質問に対する答えは簡単です。 (ほぼ)すべてのモジュールには名前があります。 de.codecentric.mymodule





などのパッケージ命名規則に準拠する必要があります de.codecentric.mymodule





、競合を避けるため。



2番目の質問に答えるために、モジュールはこの特定のモジュールのすべてのパッケージのリストを提供します。これらのパッケージはパブリックAPIと見なされるため、他のモジュールで使用できます。 クラスがエクスポートされたパッケージでない場合は、たとえパブリックであっても、モジュールの外部から誰もアクセスできません。



3番目の質問に対する答えは、このモジュールが依存するモジュールのリストです。 これらのモジュールによってエクスポートされるすべてのパブリックタイプは、依存モジュールからアクセス可能です。 Jigsawチームは、「 read 」という別のモジュールという用語を導入しようとしています。



これは現状の大きな変化です。 Java 8までは、クラスへのパスにあるすべてのパブリックタイプは、他のタイプで使用できました。 Jigsawの出現により、Javaタイプのアクセシビリティシステムは















モジュール化されたJDK



モジュールの依存関係は非循環グラフを形成し、循環依存関係を回避する必要があります。 この原則を実装するために、Jigsawチームは次の大きな問題を解決する必要がありました。モジュールに侵入するJavaランタイム環境は、報告されているように、循環的および非論理的な依存関係でいっぱいです。 結果はグラフです:







グラフのベースはjava.baseです。 これは、着信エッジのみを持つ唯一のモジュールです。 作成する各モジュールはjava.base





読み取り java.base





java.base





宣言するかどうか-暗黙の拡張java.lang.Object





と同様 java.lang.Object





java.base





java.lang





などのパッケージをエクスポートします java.lang





java.util





java.util





java.math





java.math





など



JDKのモジュール化により、使用するJavaランタイムモジュールを指定できるようになりました。 したがって、 java.desktop





モジュールを読まない限り、アプリケーションはSwingまたはCorbaをサポートする環境を使用しないでください。 java.desktop





またはjava.corba





java.corba





。 このようなトリミングされた環境の作成については、第2部で説明します。

しかし、かなりドライな理論...



ポヒミン



この記事のすべてのコードは、サンプルのコンパイル、パッケージ化、実行用のシェルスクリプトを含め、 ここから入手できます



ここで検討する実際的なケースは非常に単純です。 モジュールde.codecentric.zipvalidator





があります de.codecentric.zipvalidator





郵便番号の特定の検証を実行します。 このモジュールはde.codecentric.addresschecker





モジュールによって読み取られます de.codecentric.addresschecker





(郵便番号だけでなく、ここでは複雑化しないようにこれを行いません)。

zipバリデーターは、次のmodule-info.java





ファイルで説明されています module-info.java









module de.codecentric.zipvalidator {

de.codecentric.zipvalidator.apiをエクスポートします。

}



したがって、このモジュールはパッケージde.codecentric.zipvalidator.api





エクスポートします de.codecentric.zipvalidator.api





また、他のモジュールは読み取りません( java.base





を除く java.base





) このモジュールは、アドレスチェッカーによって読み取られます。



 module de.codecentric.addresschecker{ exports de.codecentric.addresschecker.api; requires de.codecentric.zipvalidator; }
      
      







ファイルシステムの一般的な構造は次のとおりです。



 two-modules-ok/ ├── de.codecentric.addresschecker │ ├── de │ │ └── codecentric │ │ └── addresschecker │ │ ├── api │ │ │ ├── AddressChecker.java │ │ │ └── Run.java │ │ └── internal │ │ └── AddressCheckerImpl.java │ └── module-info.java ├── de.codecentric.zipvalidator │ ├── de │ │ └── codecentric │ │ └── zipvalidator │ │ ├── api │ │ │ ├── ZipCodeValidator.java │ │ │ └── ZipCodeValidatorFactory.java │ │ ├── internal │ │ │ └── ZipCodeValidatorImpl.java │ │ └── model │ └── module-info.java
      
      







モジュールは、このモジュールと同じ名前のディレクトリに配置されるという合意があります。

最初の例では、すべてが素晴らしく見えます。ルールとAddressCheckerImpl





クラスで厳密に動作します AddressCheckerImpl





ZipCodeValidator





のみにアピールしZipCodeValidator





ZipCodeValidator





およびZipCodeValidatorFactory





ZipCodeValidatorFactory





エクスポートされたパッケージから:



 public class AddressCheckerImpl implements AddressChecker { @Override public boolean checkZipCode(String zipCode) { return ZipCodeValidatorFactory.getInstance().zipCodeIsValid(zipCode); } }
      
      







ここでjavac





実行します javac





バイトコードを生成します。 zipvalidator





をコンパイルするには zipvalidator





(もちろん、アドレスチェッカーがzipvalidatorを読み取るため、最初に行う必要があります)



 javac -d de.codecentric.zipvalidator \ $(find de.codecentric.zipvalidator -name "*.java")
      
      







zipvalidatorはユーザーモジュールに依存しないため、モジュールの話はまだありません。 find





find





.java





ファイルのリストを作成するのに役立ちます .java





指定されたディレクトリ内。

しかし、 javac





に伝えるように javac





モジュールの構造について、コンパイル時に これを行うには、jigsawにスイッチを入力しますmodulepath





modulepath





または-mp





-mp









アドレスチェッカーをコンパイルするには、次のコマンドを使用します。



javac -modulepath。 -d de.codecentric.addresschecker \

$(de.codecentric.addresschecker -name "* .java"を見つけます)



modulepathを使用して、javacにコンパイル済みモジュール(この場合はこれ)の場所を指示します。クラスパススイッチに似たものを取得します。



ただし、複数のモジュールを個別にコンパイルするのは少し面倒なようです-他の-modulesourcepathスイッチを使用して複数のモジュールを一度にコンパイルする方が良いでしょう:



 javac -d . -modulesourcepath . $(find . -name "*.java")
      
      







このコードは、すべてのサブディレクトリを検索します。 モジュールのディレクトリ。モジュールに含まれるすべてのJavaファイルをコンパイルします。

すべてをコンパイルしたら、自然に何が起こったのか試してみたい:



 java -mp . -m de.codecentric.addresschecker/de.codecentric.addresschecker.api.Run 76185
      
      







繰り返しますが、モジュールへのパスを示し、コンパイルされたモジュールの場所をJVMに伝えます。 また、メインクラス(およびパラメーター)も設定します。



やあ、結論はこうだ:



 76185 is a valid zip code
      
      







モジュラージャー



ご存じのとおり、Javaの世界では、jarファイルでバイトコードを送受信することに慣れています。 Jigsawは、モジュール式jarの概念を導入しています。 モジュラーjarは通常のjarに非常に似ていますが、コンパイルされたmodule-info.class.





も含まれていmodule-info.class.





module-info.class.





これらのファイルが目的のターゲットバージョン用にコンパイルされている場合、これらのアーカイブには下位互換性があります。 module-info.java





有効な型名ではないため、コンパイルされたmodule-info.class





module-info.class





古いJVMでは無視されます。



zipvalidatorのjarを作成するには、次のように記述します。



 jar --create --file bin/zipvalidator.jar \ --module-version=1.0 -C de.codecentric.zipvalidator
      
      







出力ファイル、バージョン(実行時のJigsawでのモジュールのいくつかのバージョンの使用は個別に指定されていませんが)、およびパッケージ化されるモジュールを示します。

addresscheckerにはメインクラスもあるため、指定できます。



 jar --create --file=bin/addresschecker.jar --module-version=1.0 \ --main-class=de.codecentric.addresschecker.api.Run \ -C de.codecentric.addresschecker .
      
      







メインクラスはmodule-info.java





指定されていません module-info.java





、ご想像のとおり(最初はJigsawチームがそうする予定でした)が、通常はマニフェストに記述されています。



この例を実行すると



 java -mp bin -m de.codecentric.addresschecker 76185
      
      







前の場合と同じ答えが得られます。 再びモジュールへのパスを指定します。この場合、jarを書き込んだbinディレクトリに移動します。 addresschecker.jarマニフェストにはすでにこの情報があるため、メインクラスを指定する必要はありません。 モジュール名を-m





スイッチに指定するだけです -m









これまでのところ、すべてが簡単で快適です。 次に、モジュールを少しいじって、無秩序になり始めた場合のコンパイルと実行中のジグソーの動作を見てみましょう。



エクスポートされていないタイプの使用



この例では、使用してはならない別のモジュールから型にアクセスするとどうなるかを見てみましょう。



AddressCheckerImpl





このファクトリーに飽き飽きしているから AddressCheckerImpl





、実装を変更します



 return new ZipCodeValidatorImpl().zipCodeIsValid(zipCode);
      
      







コンパイルしようとすると、期待どおりになります



 error: ZipCodeValidatorImpl is not visible because package de.codecentric.zipvalidator.internal is not visible
      
      





そのため、コンパイル時にエクスポートされない型を使用すると機能しません。

しかし、私たちは賢い人なので、少しごまかしてリフレクションを使用します。



 ClassLoader classLoader = AddressCheckerImpl.class.getClassLoader(); try { Class aClass = classLoader.loadClass("de.[..].internal.ZipCodeValidatorImpl"); return ((ZipCodeValidator)aClass.newInstance()).zipCodeIsValid(zipCode); } catch (Exception e) { throw new RuntimeException(e); }
      
      





完全にコンパイルされた、実行しましょう。 しかし、いや、ジグソーをだますのは簡単ではありません。



 java.lang.IllegalAccessException: class de.codecentric.addresschecker.internal.AddressCheckerImpl (in module de.codecentric.addresschecker) cannot access class [..].internal.ZipCodeValidatorImpl (in module de.codecentric.zipvalidator) because module de.codecentric.zipvalidator does not export package de.codecentric.zipvalidator.internal to module de.codecentric.addresschecker
      
      







したがって、Jigsawには、コンパイル時だけでなく、実行時のチェックも含まれます! そして、私たちが間違ったことを非常に明確に教えてくれます。



循環依存



次の場合、アドレスチェッカーモジュールAPIにzipvalidatorが使用できるクラスが含まれていることが突然わかりました。 怠zyなので、クラスを別のモジュールにリファクタリングする代わりに、addresscheckerの依存関係を宣言します。



 module de.codecentric.zipvalidator{ requires de.codecentric.addresschecker; exports de.codecentric.zipvalidator.api; }
      
      







循環的な依存関係は定義上禁止されているため、コンパイラは私たちの邪魔になります(共通の目的のため)。



 ./de.codecentric.zipvalidator/module-info.java:2: error: cyclic dependence involving de.codecentric.addresschecker
      
      







これを行うことはできません。コンパイル中であっても、事前に警告されます。



暗黙の読みやすさ



機能を拡張するために、新しいモジュールde.codecentric.zipvalidator.model





導入してzipvalidatorを継承することにしました de.codecentric.zipvalidator.model





妥当なブール値だけでなく、検証結果の特定のモデルが含まれています。 新しいファイル構造は次のとおりです。



 three-modules-ok/ ├── de.codecentric.addresschecker │ ├── de │ │ └── codecentric │ │ └── addresschecker │ │ ├── api │ │ │ ├── AddressChecker.java │ │ │ └── Run.java │ │ └── internal │ │ └── AddressCheckerImpl.java │ └── module-info.java ├── de.codecentric.zipvalidator │ ├── de │ │ └── codecentric │ │ └── zipvalidator │ │ ├── api │ │ │ ├── ZipCodeValidator.java │ │ │ └── ZipCodeValidatorFactory.java │ │ └── internal │ │ └── ZipCodeValidatorImpl.java │ └── module-info.java ├── de.codecentric.zipvalidator.model │ ├── de │ │ └── codecentric │ │ └── zipvalidator │ │ └── model │ │ └── api │ │ └── ZipCodeValidationResult.java │ └── module-info.java
      
      







クラスZipCodeValidationResult





ZipCodeValidationResult





-「短すぎる」、「長すぎる」などの形式のインスタンスを持つ単純な列挙

クラスmodule-info.java





module-info.java





このように継承しました:



 module de.codecentric.zipvalidator{ exports de.codecentric.zipvalidator.api; requires de.codecentric.zipvalidator.model; }
      
      







これで、ZipCodeValidatorの実装は次のようになります。



 @Override public <strong>ZipCodeValidationResult</strong> zipCodeIsValid(String zipCode) { if (zipCode == null) { return ZipCodeValidationResult.ZIP_CODE_NULL; [snip] } else { return ZipCodeValidationResult.OK; } }
      
      







addresscheckerモジュールは、戻り値の型として列挙型を取得できるようになりましたので、続行できますか? いや! コンパイルの結果:



 ./de.codecentric.addresschecker/de/[..]/internal/AddressCheckerImpl.java:5: error: ZipCodeValidationResult is not visible because package de.codecentric.zipvalidator.model.api is not visible
      
      





アドレスチェッカーのコンパイル中にエラーが発生しました-zipvalidatorは、公開APIでzipvalidatorモデルからエクスポートされたタイプを使用します。 アドレスチェッカーはこのモジュールを読み取らないため、このタイプにアクセスできません。



この問題には2つの解決策があります。 明らかに:アドレスチェッカーからzipvalidatorモデルに読み取りエッジを追加します。 ただし、これは滑りやすい勾配です。zipvalidatorでの作業にのみ必要な場合、なぜこの依存関係を宣言する必要があるのでしょうか。 zipvalidatorは、必要なすべてのモジュールにアクセスできることを保証すべきではありませんか? すべきであるかもしれない-ここで私たちは暗黙の読みやすさになります。 publicキーワードを必要な定義に追加することにより、すべてのクライアントモジュールに別のモジュールも読み込む必要があることを伝えます。 例として、更新されたmodule-info.java





クラスを考えます module-info.java





zipvalidator:



 module de.codecentric.zipvalidator{ exports de.codecentric.zipvalidator.api; requires public de.codecentric.zipvalidator.model; }
      
      







public





キーワード public





zipvalidatorを読み取るすべてのモジュールに、モデルも読み取る必要があることを伝えます。 クラスパスを別の方法で操作する必要がありました。たとえば、すべての依存関係もクライアントからアクセス可能であることを保証する必要がある場合、Maven POMに依存できませんでした。 これを実現するには、パブリックAPIの一部である場合は明示的に指定する必要がありました。 これは非常に美しいモデルです。クラス内でのみ依存関係を使用する場合、クライアントにとって重要なことは何ですか? また、クラス外で使用する場合は、直接報告する必要もあります。



まとめ



それで最初の部分は終わりました。 モジュールごとに回答する必要がある3つの質問と、Javaランタイムのモジュール化について説明しました。 次に、2つのモジュールで構成される単純なJavaアプリケーションをコンパイル、起動、およびパッケージ化する例を見てみました。 次に、実際の例を使用して、確立されたルールの違反にモジュールシステムがどのように応答するかを調べました。 さらに、機能を拡張して、3番目のモジュールを検討し、暗黙の読みやすさの概念について説明しました。



次のセクションでは、次の問題に対処します。






All Articles