読者です 読者をやめる 読者になる 読者になる

見習いプログラミング日記

Java EE を中心に色々なことを考えてみます。目指せ本物のプログラマ。

JavaOne 2015 2日目メモ (10/26)

今日から各種テクニカルセッションの開始。jigsawのセッションにいくつか参加。

[TUT4416] Preventing Errors Before They Happen

The Checker Frameworkを活用して、いかにコンパイル時にバグを検出するかを紹介するセッション。Checker Framework 聞き覚えがあるなぁと思っていたら、2013年のJJUG CCCで@kimuchi583さんによって紹介されていた。

セッションによると2013年の1年間で、$312 billionのコストが世界中のソフトウェアのバグによって発生しているとのこと。Javaの型システムによる安全性は思ったより抜け道が多く、ランタイム例外が発生しやすい。

例えば以下のコード。静的にチェックできそうだが、ランタイム例外になる。

// UnSupportedOperationException
Collections.emptyList().add("one");

また、以下のようなSQLインジェクションの恐れがあるコードも、可能であればコンパイル時にチェックしたい。

dbStatement.executeQuery(userInput);

このように、実行時まで検出できない問題を、アノテーションの付与によってコンパイル時にチェックするツールがChecker Frameworkである。

例えばjava.util.Dateはミュータブルである。HashMapのキーにDateを利用し、意図しないメソッドがDateを変更すると、map.get(date)時にnullが返ってきてNullPointerExceptionの温床となる。このようなケースは@ImmutableをDateに付与する。Java8のタイプアノテーションの導入により、ローカル変数の型に対してもアノテーションを付与することができる。

public class App {
    public static void main(String ... args) {
        new App().date();
    }
    
    private void date() {
        @Immutable Date date = new Date();
        date.setHours(3);    // コンパイル時にエラー検知
        System.out.println(date);
    }
}

他にも、@NonNullでは、nullが入ってきたときにコンパイル時エラーにできる。

public static void main(String ... args) {
    new App().print(null);   // コンパイル時にエラー検知
}
    
private void print(@NonNull String message) {
    System.out.println(message);
}

CheckerFrameworkは、コンパイル処理の後に、型チェックプラグインとしてこのような処理を実装している。CheckerFrameworkをダウンロードすると、チェック機能が有効となるjavacが含まれている。-processorにチェックしたい種類のプロセッサを指定するらしく、@Immutableを読み込んでチェックしたい場合は以下のようにIGJCheckerを指定する。Dateに対する@Immutableの例では、date.setHoursするとjavacからエラーが返ってくる。

checker-framework-1.9.7/checker/bin/javac -Xlint:deprecation -processor org.checkerframework.checker.igj.IGJChecker App.java
App.java:31: 警告: [deprecation] DateのsetHours(int)は非推奨になりました
        date.setHours(3);
            ^
警告1個

他にもSQLインジェクションに繋がる可能性のあるパラメータは@Untainted、正しい正規表現文字列かの検証に@Regexなど、様々なチェックルールが用意されている。

Mavenなどのビルドツールへの組み込み方については公式ドキュメント参照。セッションでは、CheckerFrameworkのEclipseプラグインの紹介もあり、コードを書きながらバグを検知するデモが行われた。

FindBugsなどの既存ツールとの違いは、NPEの可能性を見逃さないこと。セッションでは、FindBugsでは検出できなかったNPEが、CheckerFrameworkなら検出できると紹介されていた。デメリットはアノテーションを付与しなければいけないことだが、@Nonnullなどを実際に付与するべき部分は意外と少なく、負担よりも見返りの方が大きいとのこと。GoogleやWall Streat Journalでも使われていることを紹介していた。

[CON2554] Java EE 8 Work in Progress

Java EE 8 の検討状況に関するセッション。内容は去年とあまり変わらず(去年の主な内容はJavaOne報告会2014のスライド参照)。去年見かけなかった気がするもののみまとめ。いずれもまだEarly Draft Reviewの状況であるため、今後変更される可能性あり。

JSON-P1.1: ストリームAPIの適用のアイディア

JsonArray contacts = ...;
JsonArray femaleNames = contacts.getValueAs(JsonObject.class).stream()
                                    .filter(x -> "F".equals(x.getString("gender"))
                                    .map(x -> x.getString("name"))
                                    .collect(JsonCollectors.toJsonArray());

JMS2.1: MDBのシンプル化のアイディア。
コネクションファクトリの取得、MessageListenerインタフェースの実装が不要となる。従来はMessage.getXXXでメッセージに含まれるパラメータを取得していたが、これもコールバックメソッドの引数に@MessagePropertyにより設定される。

@MessageDriven
public class MyMDB {
    @JMSQueueListener(destinationLookup="java:global/requestQueue")
    public void myCallback(@MessageProperty("price") long price) {
        ....
    }
}

Java EEセキュリティ: パスワードエイリアスの標準APIサポート。
APサーバ固有のAPIに依存せずに、APサーバに事前に登録されたパスワードエイリアスを利用する。

@DataSourceDefinition(
    name="java:/app/MyDataSource"
    className="com.example.MyDataSource"
    ...
    user="duke",
    password="${ALIAS=dulePassword}")

Java EEは今後どうなるかと思っていたが、JAX-RSCDIの導入時のような大きな変化はないが、マイナーアップデートレベルで徐々に進んでいる。

[CON5118] Introduction to Modular Development

jigsawの導入セッション。
モジュールとは、パッケージを束ねた新しい単位。module -> package -> class -> field, Method の順番でグルーピングしている。モジュールのメタ情報は、jarファイルごとに含めるmodule-info.javaに定義する。ソースファイルのトップレベルに配置する。

module-info.java
com/foo/bar/alpha/Alpha.java
com/foo/bar/alpha/AlphaFactory.java
com/foo/bar/beta/Beta.java

module-info.javaの中身は以下のように、依存するモジュール(requires)と、他のモジュールに公開するパッケージ(exports)が含まれる。例えば以下の例では、JDBCのモジュールに依存し、2つのパッケージを外部モジュールに参照可能とする。exportsに含まれていないパッケージはpublicであっても、他のモジュールから参照できない。 jigsawの登場により『public ≠ accessible』となった。

module com.foo.bar {
    requires java.sql
    export com.foo.bar.alpha:
    export com.foo.bar.beta;
}

モジュールの推移的依存について。
例えば、java.sqljava.logging モジュールに依存している。java.sqlモジュールに依存するアプリケーションのモジュールでもjava.sqlに依存する場合は、明示的にrequiresを書く必要はない。以下のjava.sqlのように『requires public』となっていると、このモジュールの参照元にもjava.loggingモジュールは公開される。

module java.sql {
    requires public java.logging;
}

各モジュールはバージョンを持つ。例えばJDK同梱ライブラリの場合、java -listmodsでモジュールのバージョンが表示される。以下の結果は、JDK9 Early Access with Project Jigsawで実行した結果。

$ java -listmods
java.activation@9.0
java.annotations.common@9.0
java.base@9.0
java.compact1@9.0
...

コンパイル時には、module-info.javaと一緒にコンパイルする。このsrcの下にモジュール名のフォルダ(com.foo.baz)を切る構成は、jigsawのQuick Start Guideでも見かけた。Jigsaw利用時は一般的になるのだろうか。

javac -d mods/com.foo.baz 
src/com.foo.baz/module-info.java src/com.foo.baz/com/foo/baz/Bazooka.java

コンパイル時に依存モジュールがある場合は、-classpathではなく、-modulepath dir1:dir2:dir3 のようにモジュールが置かれているパスを指定する。-modulepathは-mpと省略記法にすることも可能。この点はクラスパスの-cpと似ている。

javac -modulepath mods -d mods/com.foo.bar
src/com.foo.bar/module-info.java
src/com.foo.bar/com/foo/bar/alpha/Alpha.java

実行時にも、クラスパスではなくモジュールの格納ディレクトリを-modulepathで指定。メインクラスを含むモジュールは-mで指定。
以下の例ではcom.foo.appモジュールの、メインクラスcom.foo.app.Mainを指定。

java -modulepath mods -m com.foo.app/com.foo.app.Main

JARパッケージングは今までと同じ方法も可能。module-info.classを一緒に含めること。module-infoが入っているjarに対して、jar -pすると、モジュール情報が表示される。

$ jar --file mlib/app.jar -p
Name:
  com.foo.app
Requires:
  com.foo.bar
  java.base [MANDATED]
  java.sql
Main class:
  com.foo.app.Main

Java9より、jlinkというコマンドが含まれており、アプリのモジュールと実行可能なイメージとして配布可能。写真を取ってなかったので、以下の例はjigsaw Quick Startを試したときのもの。以下のようにjlinkすると、

jlink --modulepath $JAVA_HOME/jmods:mlib --addmods com.greetings --output greetingsapp

以下のようにjavaコマンドを含む、実行に必要なファイルが--outputに指定したディレクトリに出力される。

./greetingsapp
./greetingsapp/bin
./greetingsapp/bin/com.greetings
./greetingsapp/bin/java
./greetingsapp/bin/keytool
./greetingsapp/conf
...

以下のように、Unix系OSの実行ファイルのような感覚で起動することができる。

cd greetingsapp/bin/
# com.greetingsモジュールのmainメソッドの起動
./com.greetings

他にも java -mp mlib -addmods com.foo.baz -cp app.jar com.foo.app.Main のように、クラスパスとモジュールパスの組み合わせの話が出たが、詳細は聞き取れなかった。

ここからは感想。jigsawはまずhttps://jdk9.java.net/jigsaw/からEarly Accessを取得して、Quick Startを試してみると良いと思った。

[CON6821] Advanced Modular Development

Jigsawに対応していないjackson-core/jackson-databind/jackson-annotationsの各jarに依存するアプリを、どのようにモジュール対応させていくかの話。聞き取れなかった部分も多かったため、後日復習してからまとめる。わかった範囲でメモ。

トップダウンアプローチ:

  • jacksonの各jarはそのまま使い、アプリをモジュール対応させる場合のアプローチ
  • まず、アプリが依存しているモジュールやjarを jdepsコマンド で抽出する
  • 特にmodule-info.javaが含まれていないライブラリでも、Automatic modulesとしてモジュールとして扱える?
    • その代わりjarに含まれるすべてのパッケージはexportsされる
    • 今までのjarをモジュールとして流用した場合、すべてのモジュールに対してはrequiresになる? (ここ間違ってるかも)
  • アプリのmodule-info.javaには以下のように記載
module myapp {
    requires mylib;
    requires java.base;
    requires java.sql;
    requires jackson.core;
    requires jacson.databind;
}

ボトムアップアプローチ:

  • jacksonをモジュール対応させ、モジュール化したjacksonにアプリのモジュールが依存するアプローチ
  • トップダウンと同じように jdepsコマンド でjacksonが依存するモジュールやjarを抽出
  • jdeps -genmoduleinfo src *.jar でmodule-info.javaを自動生成?
    • 自動生成したmodule-info.javaは、全てのパッケージがexportsされているため、モジュールプライベートにしたいパッケージは消す
  • リフレクションで依存していた場合については、聞き取れなかった

[CON3942] What’s Coming in JMS 2.1

前半はJMS2.0 (Java EE7) の振り返りであったため省略。
後半から、現在検討中のJMS2.1の話。前述の [CON2554] Java EE 8 Work in Progress で出てない話としては、以下のようなコード例があった。

@MDBが1クラス1リスナに対して、複数のキューに対するリスナを盛り込める案:

@MessageDriven
public class MyFlexibleMDB {
    @JMSQueueListener(destionationLookup="java:global/queue1")
    public void myMessageCallback1(String messageText) {
        ...
    }
    @JMSQueueListener(destionationLookup="java:global/queue2")
    public void myMessageCallback2(Stromg messageText) {
        ...
    }
}

再接続ポリシーなど、JMS実装固有のプロパティをアノテーションで指定する案:
GlassFishの再接続設定の場合。従来はasadmin経由でAPサーバに設定したが、コードに書けるようにすることを目的とする。

@MessageDriven
public class MyFlexibleMDB {
    @JMSQueueListener(destinationLookup="java:global/queue1")
    @JMSListenerProperty(name="reconnectAttempts" value="10")
    @JMSListenerProperty(name="reconnectInterval", value="10000")
    public void myMessageCllback1(String messageText) {
        ...
    }
}

CDI管理Beanでメッセージ受信することも検討しているらしいが、色々と課題がある。

  • いつリスナクラスのインスタンスを生成する? @Dependentで毎回生成? @ApplicationScopedにする?
    • @ApplicationScopedにして、マルチスレッド呼び出しを許容すると、ユーザはスレッドセーフの意識が必要となる
    • では呼び出し毎にインスタンスを分けるなら、いくつプールする?
    • @MaxInstance(10) のようなアノテーションCDI管理Beanに付与して、プール数を指定する?
  • CDIイベントの機能と似たようなものになってしまう (永続化の有無はあるが)

ここからは感想。個人的にJMSは1.1のイメージが抜けなくて、あまり使いたくなかったが、ここまでシンプルになるなら嬉しい。Java EEに大きな機能追加がなくても、小さなマイナーアップデートは続いてほしい。

[CON6823] Project Jigsaw: Under the Hood

Jigsawをより深く紹介するセッション。
聞き取れなかったので、スライド公開後に復習する。わかった分だけメモ。

  • 今までのスコープはpublic/protected/パッケージ/private
  • Jigsawによるモジュール化後は以下のようになる。publicが細分化。
    • public for everyone (exportsしたもの)
    • public but only to specific modules (特定のモジュールに対して公開。どうやる?)
    • public only within a module (モジュール内のみ。exportsしてないpublicパッケージ)
    • proceted / パッケージプライベート / private
  • モジュールが入っても、JDK9には3層のクラスローダ(bootstrap/ext/application)は残っている
    • クラスローダごとに読まれるモジュールが違う。
    • 例えばbootstrapクラスローダでは、JDKのコアモジュールとなるjava.baseがロードされる
  • モジュール間の循環参照はいけない。依存関係のグラフ解決ができない。