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

こんにちは、Habr。



少し遅れて、Jigsawプロジェクトに関する記事の第2部とCodecentricブログで公開されているJava 9を公開します。 最初の部分の翻訳はこちらです。





これは、ジグソーパズルプロジェクトを詳しく調べたい人のための記事の第2部です。 第1部では、モジュールとは何か、Javaランタイムランタイムがどのようにモジュール化されるかについて簡単に説明しました。 次に、モジュラーアプリケーションのコンパイル、パッケージ化、実行の簡単な例を見てみました。



ここでは、次の質問に答えようとします。







基礎としてパート1の例を取り上げ、引き続き作業してください。 コードはまだここにあります。



特定のモジュールへの読み取りアクセスを許可する



最初の部分では、Jigsawの一部としてJavaアクセシビリティがどのように開発されているかについて話しました。 言及されているが、次のように明確にされていないアクセシビリティレベルの1つは、「一部のモジュール、このモジュールを読み取るモジュールに対してパブリック」です。 そのため、エクスポートされたパッケージの読み取りを許可するモジュールの範囲を制限できます。 de.codecentric.zipvalidator





の開発者としましょう de.codecentric.zipvalidator





開発者が嫌いde.codecentric.nastymodule





de.codecentric.nastymodule





、したがって彼らはmodule-info.java





を変更できmodule-info.java





module-info.java





このように:



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







したがって、 addresschecker





のみ addresschecker





zipvalidator





APIにアクセスできます zipvalidator





。 この指示はパッケージレベルで実行されるため、一部のパッケージへのアクセスを制限すると同時に、他のパッケージへのフルアクセスを与えることを妨げるものはありません。 この方法は、 修飾エクスポートと呼ばれます 。 モジュールがde.codecentric.nastymodule





場合 de.codecentric.nastymodule





de.codecentric.zipvalidator.api





から任意のタイプにアクセスしようとします de.codecentric.zipvalidator.api





コンパイルエラーが発生します。

 ./de.cc.nastymodule/de/cc/nastymodule/internal/AddressCheckerImpl.java:4: error: ZipCodeValidatorFactory is not visible because package de.cc.zipvalidator.api is not visible
      
      







注:プログラムはmodule-info.java





誓いません module-info.java





zipvalidator





以来 zipvalidator





基本的に、 nastymodule





可視パッケージをエクスポートできます nastymodule





。 たとえば、アプリケーションの内部構造をモジュール化したいが、内部モジュールのエクスポートされたパッケージをクライアントと共有したくない場合に、限定エクスポートを適用できます。



モジュールバージョン間の競合



多くの場合、推移的な依存関係を介して、異なるバージョンのライブラリが同じアプリケーションに入ります。つまり、同じモジュールがモジュールへのパスに2回現れることがあります。 2つのシナリオがすぐに思い浮かびます。







最初のシナリオに従ってアプリケーションをコンパイルしてみましょう。 zipvalidator





コピー zipvalidator







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







重複するモジュールは異なるディレクトリにありますが、モジュール名は変更されません。 コンパイル時にジグソーパズルはこれにどのように反応しますか?



 ./de.codecentric.zipvalidator.v2/module-info.java:1: error: duplicate module: de.codecentric.zipvalidator
      
      







ですから、ここで降りることは簡単です。 モジュールへのパスに同じ名前の2つのモジュールが存在する場合、Jigsawはコンパイルエラーをスローします。



2番目のケースはどうですか? ディレクトリ構造は同じままですが、現在は両方のzipvalidatorsに異なる名前が付けられています( de.codecentric.zipvalidator.v{1|2}





de.codecentric.zipvalidator.v{1|2}





)、addresscheckerはこれらの名前の両方を読み取ります。



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







ほとんどの場合、ここでコンパイルされませんか? 同じパッケージをエクスポートする2つのモジュールを読み取りますか? コンパイルが判明しました。 私自身は驚きました。コンパイラは状況を認識しますが、そのような警告によってのみ制限されます。



 ./de.cc.zipvalidator.v1/de/codecentric/zipvalidator/api/ZipCodeValidator.java:1: warning: package exists in another module: de.codecentric.zipvalidator.v2
      
      







開発者はそのような警告をすぐに無視して、アプリケーションを起動します。 しかし、ジグソーは明らかに、実行時に表示されるものが好きではありません。



 java.lang.module.ResolutionException: Modules de.codecentric.zipvalidator.v2 and de.codecentric.zipvalidator.v1 export package de.codecentric.zipvalidator.api to module de.codecentric.addresschecker
      
      







私の意見では、コンパイル時のエラーをより正確に特定することはできません。 ニュースレターで、なぜこのようなオプションが選ばれたのかを尋ねましたが、執筆の時点では、私は答えを受け取っていません。



自動モジュールと匿名モジュール



これまで、完全にモジュール化された環境で作業してきました。 しかし、非モジュラーJarファイルを処理しなければならない非常にありそうなケースではどうすればよいでしょうか? これは、 自動モジュール名前のないモジュール作用する場所です。



自動モジュールから始めましょう。 自動モジュールは、モジュールパスで提供されるjarファイルです。 そこに書いたら、このモジュールに関する次の3つの質問に答えることができます。



Q:彼の名前は何ですか?

A:これは、jarファイルの名前です。 モジュールへのパスにguava.jarファイルを配置すると、自動グアバモジュールが取得されます。



また、guava-18.0は有効なJava識別子ではないため、MavenリポジトリからJarを直接使用することはできません。



Q:何をエクスポートしますか?

A:自動モジュールは、すべてのパッケージをエクスポートします。 したがって、すべてのパブリックタイプは、自動モジュールを読み取るすべてのモジュールで使用できます。



Q:彼は何を必要としますか?

A:自動モジュールは、他のすべてのモジュール(*すべて*)を読み取ります(名前のないものを含む、詳細は以下を参照)。 これは重要です! 自動モジュールから、任意のモジュールのすべてのエクスポートされたタイプにアクセスできます。 この瞬間はどこにも示されるべきものではなく、暗示されています。



例を考えてみましょう。 zipvalidatorでcom.google.common.base.Stringsを使用し始めています。 このアクセスを許可するには、自動Guavaモジュールの読み取りエッジを定義する必要があります。



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







コンパイルするには、モジュールへのパスでguava.jarファイルを指定する必要があります(../jarsディレクトリにあります)。

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







すべてがコンパイルされ、正常に実行されます。



(ところで、この例を実行するのはそれほど簡単ではありませんでしたjdk.management.resource





ビルドの作業中に、いくつかの問題に遭遇しました。システムがjdk.management.resource





モジュールへの依存関係を呪いました jdk.management.resource





。 私はニュースレターでこれについて尋ねました 。議論はこちらです。



私のソリューションでは、「アーリー」ビルド(アーリーアクセスビルド)を使用しなかったが、JDKは自分でビルドしたと言わなければなりません。 OSX Mavericksを使用する場合、スレッドに記述されているため、まだいくつかの問題がありました。メイクファイルを変更する必要がありましたが、最終的にはすべてが正しくなりました。 次のリリースで作業するときは、他の問題に対処する必要があります)。



今こそ、ジグソーパズルに切り替えるときに不可欠な魔法の杖を紹介するときです。 このツールはjdeps





と呼ばれjdeps





jdeps





。 モジュール化されていないコードをスキャンし、依存関係を通知します。



グアバを検討してください:



jdeps -s ../jars/guava.jar

次の結論があります。

guava.jar-> java.base

guava.jar-> java.logging

guava.jar->見つかりません



これは、自動java.base





モジュールがjava.base





必要とすることを意味します java.base





java.logging





java.logging





そして...「見つかりません」?! なに -s





を削除した場合 -s





その後、 jdeps





jdeps





モジュールレベルを終了し、パッケージレベルに進みます(グアバには多くのパッケージがあるため、リストは少し短くなります)。



  com.google.common.xml (guava.jar) -> com.google.common.escape guava.jar -> java.lang -> javax.annotation not found
      
      







これは、パッケージcom.google.common.xml





com.google.common.xml





com.google.common.escape





依存 com.google.common.escape





モジュール自体、 java.lang





java.lang





これはよく知られており、 javax.annotation





アノテーションから javax.annotation





見つかりません。 javax.annotation





が含まれているため、JSR-305型のjarが必要であると結論付けました。 javax.annotation





(ところで、私はそれらなしで行うことができます-私の例では、これらのパッケージのタイプは必要なく、コンパイラもランタイムオブジェクトも必要ありません)。



名前のないモジュール



名前のないモジュールとは何ですか? 再び3つの質問に答えます。



Q:彼の名前は何ですか?

A:ご想像のとおり、彼には名前がありません



Q:何をエクスポートしますか?

A:名前のないモジュールは、すべてのパッケージを他のモジュールにエクスポートします。 これは、他のモジュールから読み取ることができることを意味するものではありません。名前がなく、要求することはできません。 コマンドには名前のない名前が必要です。 動作しません。



Q:彼は何を必要としますか?

A:名前のないモジュールは、利用可能な他のすべてのモジュールを読み取ります。



だから、あなたのモジュールのいずれかから名前のないモジュールを読み取ることができない場合、ポイントは何ですか? 私たちの旧友であるクラスへの道は、この質問に答えるのに役立ちます。 (モジュールパスからではなく)クラスパスから読み取られた型はすべて、名前のないモジュールに自動的に配置されます。つまり、名前のないモジュールのすべての型がクラスパスを介してロードされます。 名前のないモジュールは他のすべてのモジュールを読み取るため、クラスパスを介してロードされた任意のタイプからエクスポートされたすべてのタイプを参照できます。 Java 9では、後方互換性を提供するために、クラスパスとモジュールパスの使用が個別および共同でサポートされます。 いくつかの例を見てみましょう。



きちんとしたzipvalidatorモジュールがあるが、アドレスチェッカーはまだモジュール化されておらず、 module-info.java





がないとしmodule-info.java





module-info.java





。 ソースコードの構造は次のとおりです。



 one-module-with-unnamed-ok/ ├── classpath │ └── de.codecentric.legacy.addresschecker │ └── de │ └── codecentric │ └── legacy │ └── addresschecker │ ├── api │ │ ├── AddressChecker.java │ │ └── Run.java │ └── internal │ └── AddressCheckerImpl.java ├── modulepath │ └── de.codecentric.zipvalidator │ ├── de │ │ └── codecentric │ │ └── zipvalidator │ │ ├── api │ │ │ ├── ZipCodeValidator.java │ │ │ └── ZipCodeValidatorFactory.java │ │ └── internal │ │ └── ZipCodeValidatorImpl.java │ └── module-info.java
      
      







これで、zipvalidatorにアクセスするためのレガシーコードを含むclasspathディレクトリと、zipvalidatorモジュールを含むmodulepathディレクトリがあります。 通常どおりモジュールをコンパイルできます。 継承されたコードをコンパイルするには、モジュラーコードに関する情報を提供する必要があります。 クラスパスに書き込むだけです:

 javac -d classpath/de.codecentric.legacy.addresschecker -classpath modulepath/de.codecentric.zipvalidator/ $(find classpath -name "*.java")
      
      







すべてが通常どおり機能します。



実行中に、2つの可能性があります。 すなわち:





最初のオプションを選択すると、実際には、モジュラーシステムを放棄します。 名前のないモジュールで記述するすべてのタイプは、自由に相互に連絡できるようになります。



 java -cp modulepath/de.cc.zipvalidator/:classpath/de.cc.legacy.addresschecker/ de.codecentric.legacy.addresschecker.api.Run 76185
      
      







現在使用しているJavaアプリケーションとまったく同じように機能します。



一方、モジュールパスとクラスパスの混合使用は次のように機能します。



 java -modulepath modulepath -addmods de.codecentric.zipvalidator -classpath classpath/de.codecentric.legacy.addresschecker/ de.codecentric.legacy.addresschecker.api.Run
      
      







2つのスイッチを同時に使用します: -classpath





-classpath





および-modulepath





-modulepath





-addmods





スイッチを追加 -addmods





-クラスへのパスとモジュールへのパスを混在させる場合、modulepathディレクトリ内のモジュールにアクセスすることはできませんが、どのモジュールを使用可能にするかを明示的に指定する必要があります。



このアプローチも正常に機能しますが、問題があります! 「名前のないモジュールに必要なもの」という質問に対する答えは、「他のすべてのモジュール」であることを忘れないでください。 modulepathを介してzipvalidatorモジュールを使用する場合、エクスポートされたパッケージのみを使用できます。 それ以外はすべて、実行時にIllegalAccessErrorになります。 したがって、この場合、モジュールシステムのルールに従う必要があります。



jlinkを使用してランタイムイメージを作成する



モジュールに関する十分な例; 注目に値する別の新しいツールが登場しました。 jlink





独自のJVMディストリビューションを作成するためのJava 9ユーティリティです。 最も興味深いのは、JDKの新しいモジュラーアーキテクチャのおかげで、このディストリビューションに含めるモジュールを選択できることです。 例を考えてみましょう。 アドレスチェッカーを含むランタイムイメージを作成する場合は、次のコマンドを指定します。



 jlink --modulepath $JAVA9_BIN/../../images/jmods/:two-modules-ok/ --addmods de.codecentric.addresschecker --output linkedjdk
      
      





次の3つだけを示します。







このコマンドは、次の構造を作成します。



linkedjdk /

├──ビン

│├──java

│└──keytool

├──conf

│├──net.properties

│└──セキュリティ

│├──java.policy

│└──java.security

└──lib

├──クラスリスト

├──jli

  │ └── libjli.dylib ├── jspawnhelper ├── jvm.cfg ├── libjava.dylib ├── libjimage.dylib ├── libjsig.diz ├── libjsig.dylib ├── libnet.dylib ├── libnio.dylib ├── libosxsecurity.dylib ├── libverify.dylib ├── libzip.dylib ├── modules │ └── bootmodules.jimage ├── security │ ├── US_export_policy.jar │ ├── blacklisted.certs │ ├── cacerts │ └── local_policy.jar ├── server │ ├── Xusage.txt │ ├── libjsig.diz │ ├── libjsig.dylib │ ├── libjvm.diz │ └── libjvm.dylib └── tzdb.dat
      
      







以上です。 OSX Mavericksでは、これには約47 MB​​かかります。 また、アーカイブを有効にして、本番環境ではまだ必要ないデバッグ機能を削除することもできます。 私が作成できた最もコンパクトなディストリビューションは、次のコマンドで取得されました。



 jlink --modulepath $JAVA9_BIN/../../images/jmods/:two-modules-ok/bin --addmods de.codecentric.addresschecker --output linkedjdk --exclude-files *.diz --compress-resources on --strip-java-debug on --compress-resources-level 2
      
      







私の意見では、ディストリビューションのサイズは約18 MBに縮小されています-すばらしいです。 Linuxでは、おそらく13まで縮小できます。



通話中



 /bin/java --listmods
      
      







この配布に含まれるモジュールのリストが表示されます。



 de.codecentric.addresschecker de.codecentric.zipvalidator java.base@9.0
      
      







したがって、これらのモジュールの最大数に依存するすべてのアプリケーションは、このJVMで実行できます。 しかし、このスクリプトを実行するメインクラスを取得できませんでした。 このために、私は反対に行きました。



注意深い読者は、2番目の呼び出しがjlinkに対して行われ、そこへのモジュールへのパスが最初の呼び出し中と異なることに気付くことがあります。 2番目のケースでは、binディレクトリへのパスを指定します。 このディレクトリにはモジュラーjarファイルが含まれ、addresscheckerのjarには、マニフェスト内のメインクラスに関する情報も含まれます。 jlinkユーティリティはこの情報を使用して、JVM binディレクトリに追加情報を追加します。



 linkedjdk/ ├── bin │ ├── de.codecentric.addresschecker │ ├── java │ └── keytool ...
      
      







したがって、アプリケーションを直接呼び出すことができます。 美人!



 ./linkedjdk/bin/de.codecentric.addresschecker 76185
      
      







ディスプレイ



76185 is a valid zip code









おわりに



それでジグソーとの知り合いは終わりました。 JigsawとJava 9でできることとできないことを示す一連の例を見てきました。Jigsawには、ラムダ式やtry-with





リソースを使用して簡単に補償できない基本的な変更が導入されています。 try-with





。 MavenやGradleなどのアセンブリツールからIDEまでのツールチェーン全体をモジュラーシステムに適合させる必要があります。 JavaOneカンファレンスで、Gradle Inc.のHans Docter Java 8以前でもモジュラーコードの記述を開始する方法に関するレポートを読みました。 Gradleはコンパイル時にチェックし、モジュールの整合性が損なわれると失敗します。 この(実験的)機能は、 Gradle 2.9の最新リリースに含まれていました。 面白い時間を間違いなく待っています!



Jigsawのより詳細な紹介については、 Jigsaw Projectホームページ、特に最新のJavaOne会議のJigsawに関するスライドとビデオレポートをお勧めします。



All Articles