少し遅れて、Jigsawプロジェクトに関する記事の第2部とCodecentricブログで公開されているJava 9を公開します。 最初の部分の翻訳はこちらです。
これは、ジグソーパズルプロジェクトを詳しく調べたい人のための記事の第2部です。 第1部では、モジュールとは何か、Javaランタイムランタイムがどのようにモジュール化されるかについて簡単に説明しました。 次に、モジュラーアプリケーションのコンパイル、パッケージ化、実行の簡単な例を見てみました。
ここでは、次の質問に答えようとします。
- エクスポートされたパッケージを読み取ることができるモジュールに制限を導入することは可能ですか?
- モジュールパスに存在する同じモジュールの異なるバージョンで何をする必要がありますか?
- Jigsawは非モジュラーレガシーコードとどのように相互作用しますか?
- 独自の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つのシナリオがすぐに思い浮かびます。
- モジュールは異なるディレクトリまたはモジュラーjarでコンパイル時に使用可能ですが、いずれにしても同じ名前を持っています
- 同じモジュールの異なるバージョンは異なります
最初のシナリオに従ってアプリケーションをコンパイルしてみましょう。
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つだけを示します。
- モジュールへのパス(特別なモジュールとJDKのjmodsディレクトリへのパスを含む-ここに標準のJavaモジュールがあります)
- ディストリビューションに含めるモジュール
- 出力ディレクトリ
このコマンドは、次の構造を作成します。
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に関するスライドとビデオレポートをお勧めします。