Skip to content

Instantly share code, notes, and snippets.

@wreulicke
Last active August 4, 2018 02:28
Show Gist options
  • Save wreulicke/2b144e032866f7e21c5e4660d0fb4700 to your computer and use it in GitHub Desktop.
Save wreulicke/2b144e032866f7e21c5e4660d0fb4700 to your computer and use it in GitHub Desktop.
Project Jigsaw

Project Jigsaw 和訳元

初め、このプロジェクトのゴールは次のような物でした。

  • ライブラリや大きなアプリケーションをより簡単に構築、メンテナンスしやすくする
  • 基本的にはJava SEの実装のメンテナンスやセキュリティの向上のためで、一部はJDKも対象でした。
  • アプリケーションの高速化を簡単にするため
  • 小型計算デバイスや大量のクラウドへの利用のためにJava SEやJDKを小さくすることができるようにするため

これらのゴールを目指すため、我々はJava Se 9に向けて標準のモジュールシステムを設計・実装したました。 そしてJava SE 9に適用しています。そしてその参照実装がJDK9です。 モジュールシステムは、JDKやその他のレガシーコードベースをモジュール化するためには十分強力ですが 全ての開発者に手近なものとなっています。

今、我々がこのプロジェクトで提供した目標を達成した今、実利用からのフィードバックによって モジュールシステムをより促進するための追加作業を行う予定でいます。

キーとなるドキュメントやプレゼンテーション等

JEPs

Development History

Project Jigsawは2008年の8月に始まり、初期の調査フェーズを経て 2014年にJava9へ向けて設計と実装が動き始めました。

ソースコードの再編成(JEP201)がJDK9 build 27にマージされたのが2014年の8月です。

モジュールのサポートをするためにランタイムイメージの再構造化(JEP 220)がマージされたのが2014年の12月のJDK 9 build 41です。

JCP Executive Committeeによって Java PlatformのモジュールシステムのJSR 376が承認されたのが 2014年の12月です。

2015年の8月に 多くの内部APIのカプセル化の計画(JEP 260)が投稿されました。

The State of the Module Systemの初期バージョンは JEP 261と共に モジュールシステムを含んだアーリーアクセスビルドで それは2015年の9月に公表されました。

提案された仕様に関する初期の課題が、2016年の三月に公開されました。

アップデートされたThe State of the Module Systemは、互換性やマイグレーションの新たな資料を持って 2016年の3月に公開されました。

モジュールシステム自身はJSR 376によって仕様化され、JEP 261によって実装されており、 JDK9 build 111にマージされました。 それが、2016年の3月です。

最終的に 2017年の7月にProject Jigsawは完遂して、2017年の 9月に一般利用のためにJDK 9の一部として提供されるようになりました。

Qucik Start Guide 和訳元

このドキュメントではいくつかのシンプルな例を開発者がmoduleを始められるように提供します。

このサンプルの中でファイルパスはforward slashesを使い、でpath separatorはcolonsです。 Windos Userはback slashedとpath separatorはsemicolonを使ってください。

Greetings

この最初の例では com.greetings と名付けられたモジュールで それは シンプルに Greetings! と表示するものです。 このモジュールは2つのソースから成り立っており、モジュールの宣言である、module-info.java と main classです。

習慣に則って、モジュールのソースコードはモジュールの名前のディレクトリに配置しています。

    src/com.greetings/com/greetings/Main.java
    src/com.greetings/module-info.java

    $ cat src/com.greetings/module-info.java
    module com.greetings { }

    $ cat src/com.greetings/com/greetings/Main.java
    package com.greetings;
    public class Main {
        public static void main(String[] args) {
            System.out.println("Greetings!");
        }
    }

このソースコードをコンパイルして mods/com.greetings ディレクトリに配置します。次のコマンドを使います。

    $ mkdir -p mods/com.greetings

    $ javac -d mods/com.greetings \
        src/com.greetings/module-info.java \
        src/com.greetings/com/greetings/Main.java

次に、サンプルを次のコマンドで動かしましょう。

    $ java --module-path mods -m com.greetings/com.greetings.Main

--module-pathはモジュールのパスです。この値はモジュールを含んだ1つもしくは複数のディレクトリです。 -m オプショnで指定するのはmain moduleで、この値のslash以降はそのmain モジュール内のメインクラスのクラス名です。

Greeting Worlds

次のサンプルではモジュールの宣言とorg.astroモジュールへの依存を宣言を追加します。 org.astro モジュールは org.astroパッケージのAPIを公開しています。

    src/org.astro/module-info.java
    src/org.astro/org/astro/World.java
    src/com.greetings/com/greetings/Main.java
    src/com.greetings/module-info.java

    $ cat src/org.astro/module-info.java
    module org.astro {
        exports org.astro;
    }

    $ cat src/org.astro/org/astro/World.java
    package org.astro;
    public class World {
        public static String name() {
            return "world";
        }
    }

    $ cat src/com.greetings/module-info.java
    module com.greetings {
        requires org.astro; // 追加された
    }

    $ cat src/com.greetings/com/greetings/Main.java
    package com.greetings;
    import org.astro.World;
    public class Main {
        public static void main(String[] args) {
            System.out.format("Greetings %s!%n", World.name());
        }
    }

これらのモジュールは1回で1つのモジュールをコンパイルします。 javac コマンドでcom.greetingsモジュールをコンパイルするために モジュールのパスを指定します。org.astroモジュールへの参照とそのモジュールのexportされたパッケージにあるTypeを解決するために。

    $ mkdir -p mods/org.astro mods/com.greetings

    $ javac -d mods/org.astro \
        src/org.astro/module-info.java src/org.astro/org/astro/World.java

    $ javac --module-path mods -d mods/com.greetings \
        src/com.greetings/module-info.java src/com.greetings/com/greetings/Main.java

この例では最初の例と同じ方法で実行することが可能です。

    $ java --module-path mods -m com.greetings/com.greetings.Main
    Greetings world!

Multi-module Compilation

前回の例ではcom.greetingsorg.astroのモジュールを分離してコンパイルしました。 1回のコンパイルで複数モジュールをコンパイルすることも可能です。

    $ mkdir mods

    $ javac -d mods --module-source-path src $(find src -name "*.java")

    $ find mods -type f
    mods/com.greetings/com/greetings/Main.class
    mods/com.greetings/module-info.class
    mods/org.astro/module-info.class
    mods/org.astro/org/astro/World.class

Packaging

これまでの例では、ファイルシステム上にコンパイルされたモジュールが散らばっていました。

転送やデプロイのためにmodular JARとしてモジュールをpackageすることが一般に便利でしょう。 modular JARは通常のJAR fileでトップレベルにmodule-info.classを持った物です。

次の例ではmlibディレクトリに [email protected]とcom.greeting.jarを作成します。

    $ mkdir mlib

    $ jar --create --file=mlib/[email protected] \
        --module-version=1.0 -C mods/org.astro .

    $ jar --create --file=mlib/com.greetings.jar \
        --main-class=com.greetings.Main -C mods/com.greetings .

    $ ls mlib
    com.greetings.jar   [email protected]

この例では モジュールorg.astroはパッケージされており、1.0のバージョンを示しています。 また、com.greetingsモジュールはパッケージされており、Main classがcom.greetings.Mainを示しています。そのため、com.greetingsはメインクラスを指定することなく実行することが可能です。

    $ java -p mlib -m com.greetings
    Greetings world!

上記コマンドラインの-p ショートハンドによって--module-pathの代わりになります。 jar toolにはたくさんのオプションがありますが、次の例では modular JARとしてパッケージされたjarのmodule宣言を表示する例です。

    $ jar --describe-module --file=mlib/[email protected]
    [email protected] jar:file:///d/mlib/[email protected]/!module-info.class
    exports org.astro
    requires java.base mandated

Missing requires or missing exports

この例では、前回の例で間違えて、requiresディレクティブを外してしまった場合に どうなるかを見てみましょう。

    $ cat src/com.greetings/module-info.java
    module com.greetings {
        // requires org.astro;
    }

    $ javac --module-path mods -d mods/com.greetings \
        src/com.greetings/module-info.java src/com.greetings/com/greetings/Main.java
    src/com.greetings/com/greetings/Main.java:2: error: package org.astro is not visible
        import org.astro.World;
                  ^
      (package org.astro is declared in module org.astro, but module com.greetings does not read it)
    1 error

このモジュール定義を修正して、別のミスを入れたときにどうなるかみてみましょう。 ここでは org.astro のモジュールからexportsを外してみます。

    $ cat src/com.greetings/module-info.java
    module com.greetings {
        requires org.astro;
    }
    $ cat src/org.astro/module-info.java
    module org.astro {
        // exports org.astro;
    }

    $ javac --module-path mods -d mods/com.greetings \
        src/com.greetings/module-info.java src/com.greetings/com/greetings/Main.java
    $ javac --module-path mods -d mods/com.greetings \
       src/com.greetings/module-info.java src/com.greetings/com/greetings/Main.java
    src/com.greetings/com/greetings/Main.java:2: error: package org.astro is not visible
        import org.astro.World;
                  ^
      (package org.astro is declared in module org.astro, which does not export it)
    1 error

Services

Serviceはプロバイダとコンシューマに緩い結合を許します。

この例ではサービスプロバイダとサービスコンシューマの例を示します。

  • com.socket モジュールは network socketに関係するAPIを公開しています。このAPIはcom.socketパッケージを持っており、そのパッケージを公開しています。このAPIはpluggableで、代替の実装を許します。com.socket.spi.NetworkSocketProviderが同じモジュールに入っており、com.socket.spiもexportされています。

  • com.fastsocket モジュールはサービスプロバイダです。これはcom.socket.spi.NetworkSocketProvider の実装を提供しています。 しかし、何もpackageは公開していません。

com.socket モジュールのソースコードは次のようなものです。

    $ cat src/com.socket/module-info.java
    module com.socket {
        exports com.socket;
        exports com.socket.spi;
        uses com.socket.spi.NetworkSocketProvider;
    }

    $ cat src/com.socket/com/socket/NetworkSocket.java
    package com.socket;

    import java.io.Closeable;
    import java.util.Iterator;
    import java.util.ServiceLoader;

    import com.socket.spi.NetworkSocketProvider;

    public abstract class NetworkSocket implements Closeable {
        protected NetworkSocket() { }

        public static NetworkSocket open() {
            ServiceLoader<NetworkSocketProvider> sl
                = ServiceLoader.load(NetworkSocketProvider.class);
            Iterator<NetworkSocketProvider> iter = sl.iterator();
            if (!iter.hasNext())
                throw new RuntimeException("No service providers found!");
            NetworkSocketProvider provider = iter.next();
            return provider.openNetworkSocket();
        }
    }


    $ cat src/com.socket/com/socket/spi/NetworkSocketProvider.java
    package com.socket.spi;

    import com.socket.NetworkSocket;

    public abstract class NetworkSocketProvider {
        protected NetworkSocketProvider() { }

        public abstract NetworkSocket openNetworkSocket();
    }

com.fastsocket モジュールのソースコードは次のようなものです。

    $ cat src/org.fastsocket/module-info.java
    module org.fastsocket {
        requires com.socket;
        provides com.socket.spi.NetworkSocketProvider
            with org.fastsocket.FastNetworkSocketProvider;
    }

    $ cat src/org.fastsocket/org/fastsocket/FastNetworkSocketProvider.java
    package org.fastsocket;

    import com.socket.NetworkSocket;
    import com.socket.spi.NetworkSocketProvider;

    public class FastNetworkSocketProvider extends NetworkSocketProvider {
        public FastNetworkSocketProvider() { }

        @Override
        public NetworkSocket openNetworkSocket() {
            return new FastNetworkSocket();
        }
    }

    $ cat src/org.fastsocket/org/fastsocket/FastNetworkSocket.java
    package org.fastsocket;

    import com.socket.NetworkSocket;

    class FastNetworkSocket extends NetworkSocket {
        FastNetworkSocket() { }
        public void close() { }
    }

簡単にこれらのモジュールを一緒にコンパイル可能です。 ほとんどの場合、実用ではコンシューマとプロバイダのモジュールは別でコンパイルされるでしょう。

    $ mkdir mods
    $ javac -d mods --module-source-path src $(find src -name "*.java")

最後に、com.greetings モジュールを変更してこれらのAPIを使ってみます。

    $ cat src/com.greetings/module-info.java
    module com.greetings {
        requires com.socket;
    }

    $ cat src/com.greetings/com/greetings/Main.java
    package com.greetings;

    import com.socket.NetworkSocket;

    public class Main {
        public static void main(String[] args) {
            NetworkSocket s = NetworkSocket.open();
            System.out.println(s.getClass());
        }
    }


    $ javac -d mods/com.greetings/ -p mods $(find src/com.greetings/ -name "*.java")

実行してみます。

    $ java -p mods -m com.greetings/com.greetings.Main
    class org.fastsocket.FastNetworkSocket

この出力でサービスプロバイダによって提供されているNetworkSocketのファクトリが使われていることが分かります。

The linker

jlinkはlinker toolでモジュールのセットをリンクして利用することを可能にします、推移的な依存とともに。そして独自の modular 実行イメージを作ることができます。 (See JEP220)

このツールは現在、モジュールパスにパッケージされたmodular JARかJMODフォーマットでモジュールが配置されていることを必要とします。

次の例ではサンプルの実行イメージを作ります。 com.greetingsとそれに依存するモジュールを含みます。

    jlink --module-path $JAVA_HOME/jmods:mlib --add-modules com.greetings --output greetingsapp

--module-pathの値はパッケージされたモジュールを含むディレクトリです。Windowの場合はセミコロンに置き換えてください。

$JAVA_HOME/jmods はjava.base.jmodやその他の標準やJDKのモジュールを含んだディレクトリです。

mlib ディレクトリは com.greetingsモジュールの成果物を含むディレクトリです。

jlinkは生成されるイメージをカスタマイズするために複数のオプションをサポートしています。 詳しくはjlink --helpを見てください。

--patch-module

DDoug LeaのCVSからモジュールを置き換えたい場合、ソースをコンパイルして -Xbootclasspath/p オプションを利用するでしょう。

-Xbootclasspath/p は削除され、モジュールの置き換えは --patch-moduleを利用します。 モジュールの中身を強化するために利用することが可能です。 --patch-moduleはjavacもサポートしています。モジュールの一部であるかのようにコードをコンパイル可能です。

    javac --patch-module java.base=src -d mypatches/java.base \
        src/java.base/java/util/concurrent/ConcurrentHashMap.java

    java --patch-module java.base=mypatches/java.base ...
@wreulicke
Copy link
Author

https://www.youtube.com/watch?v=p7jCvbzqnS0を見た。

  • exports directive
  • open directive
  • provides directive
  • jdeps --generate-module-info hogehoge
  • Automatic module name is jaf file name without version.

きつねさんによるモジュールのディレクティブ解説
http://d.hatena.ne.jp/bitter_fox/20160921/1474476977
SPI (ServiceLoader)がMETA-INF/servicesに書かなくても使えるようになるみたい。
http://d.hatena.ne.jp/bitter_fox/20160720/1469042323
ModuleFinderのjavadoc
http://cr.openjdk.java.net/~mr/jigsaw/spec/api/java/lang/module/ModuleFinder.html#automatic-modules

@wreulicke
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment