JavaOne 2015 4日目メモ (10/28)

JavaOne 4日目の夜には、毎年トレジャーアイランドでOracle Appreciation Event(オラクルによる感謝祭?)が行われ、有名な方のライブが行われる。今年はエルトンジョンとBECKだった。毎年寒いので、薄手のダウンなど暖かい服装が必須。

4日目は以下のセッションに参加。

[CON2709] The New HTTP Client API, Including HTTP/2 and WebSocket

JEP110で提案されている、Java SEに盛り込む新しいHTTPクライアントについて。JDK9には、HttpUrlConnectionに代わるHTTPクライアントを盛り込む計画があり、HTTP1.1/HTTP2/WebSocketのクライアントがサポートされる予定である。

セッションの前半はHTTP2の説明と、Java8から導入されたComputableFutureの振り返りであったため省略。後半から、具体的なAPIのアイディアが紹介された。いずれもまだOpenJDK9のリポジトリに含まれていないため、まだ変更が入る可能性が高い。

HTTP GETし、結果を文字列(HttpResponse.asStringメソッド)で取得:

HttpResponse resp = HttpRequest
                     .create(new URI("http://www.foo.com/")
                     .GET()
                     .response();

if (reap.statusCode() == 200) {
    String responseBody = resp.body(HttpResponse.asString());
    System.out.println(System.out.println(responseBody);
}

POSTする場合は、以下のようにbodyメソッドに設定する:

HttpResponse resp = HttpRequest
                     .create(new URI("http://www.foo.com/")
                     .body(HttpRequest.fromString("param1=1,param2=2")
                     .POST()
                     .response();

非同期実行。レスポンスボディをファイルに書き出す:

ComputableFuture<Path> future= HttpRequest.create(uri)
    .GET()
    .responseAsync()
    .thenApplyAsync(resp -> {
        if (reap.statusCode() == 200) {
            return reap.body(asFile(path));
        } else {
            throw new UncheckedIOException(new IOException());
        }
    });    //=> このメソッドチェーンの返り値はComputableFuture<Path>
future.join();  //=> thenApplyAsyncが完了するまでブロック

ExecutorServiceを渡せるので、Java EE環境でもComputableFutureが使いやすい:
(APサーバが管理しているスレッドプールManagedExecutorが渡せる)

HttpClient client = HttpClient.create()
              .executorService(Executors.newCachedThreadPool())
              .build();
HttpRequest request = client.request(URI.create("https://www.foo.com/")).GET();

HTTP2でリクエストするときは、メソッドチェーンに追加する:

HttpRequest request = client.request(URI.create("https://www.foo.com/"))
    .version(HttpClient.Version.HTTP_2)
    .GET();

セッションの中ではリソースクローズに関する言及がなかったが、HttpUrlConnectionのように、クリーンアップをどうやるかわかりくいAPIにならないことを願っている。

[CON2809] Deep Dive into Top Java Performance Mistakes in 2015

性能解析ツールのdynatrace社の方による、どんなメトリクス(ページサイズ、HTTPエラーレスポンス数などの指標)が、性能遅延に直結しやすいか実例を交えながら紹介するセッション。セッションではdynatraceによる以下のようなメトリクス収集結果のデモが紹介された。

メトリクス1: 画像などのリソース数、サイズに注意

  • アメフトのスーパーボウルのページは、1ページに434の画像などのリソース、合計20MBのサイズによりレスポンス遅延が発生していた
  • FIFAワールドカップのページで、faviconアイコンが370KBを超え、そのほかにも150KBを超えるCSS複数あって遅延
  • 大きな背景画像もかっこいいが、サイズには注意が必要
  • 遅いと思ったら、まずブラウザF12のデバッグツールで見てみる

メトリクス2: SQL大量row取得、同一SQLの繰返実行(N+1)、大量のコネクション生成

  • Room Reservertion System(会議室予約?) で1リクエストで2万回以上のSQL発行 (典型的なORMのN+1問題)
  • 1万以上のコネクション生成 (プールによるコネクション再利用漏れ)
  • データキャッシュをHashTableに全て置いて、ブロック多発による性能劣化

メトリクス3: 外部サービスの呼び出し数、WebAPI実行時のN+1問題

  • 100回以上の外部WebAPIの繰り返し呼出
  • ORMのN+1問題と同様に、キーごとに毎回APIコールすると同じ問題が起こる

メトリクス4: ロギング量

  • DEBUGレベルのログを大量出力して、log4jのcallAppenderメソッドでブロック多発

メトリクス5: GC回数、ライブヒープの増加

  • アプリケーションの改修に伴いライブヒープが一気に増加
  • 低負荷時のレスポンスタイムに変化はないが、高負荷時にライブヒープ増加に伴いCPUネックが顕在化し、大幅なレスポンス低下

他にも確認した方が良いデータ:

  • サーバログのException数
  • HTTP 40x、50xエラーレスポンス
  • DBメトリクス(SQL応答時間、発行回数)

ここからはセッション内容と関連しないメモ。有償であるdynatraceを使わずにELK(ElasticSearch+Logstash+Kibana)でメトリクスが収集できないか考える。

  • フロントエンド
    • リソースのサイズはApacheアクセスログからも取得可能 (%b)。kibanaで見るときに、xxxKBを超えるレスポンス数でフィルタすれば見れそう。
    • .jsや.cssはサイズでフィルタして、xxxKBを超えるものは、minify漏れでは?というチェックもできる。
  • DB関連
    • slow queryはログから取れる。
    • ログからのN+1の検知は難しそう。今まで通り、スロークエリが出てないがDBアクセスクラスで遅い場合は、デバッグ用途でORMのSQL出力を有効化するしかない?
    • コネクション数も、例えばPostgreSQLであればpg_stat_databaseビューのnumbackendsを拾ってくればいけそう。
  • 連携サービスのRPC呼び出し過多
    • バックエンドのAPサーバ群のHTTPアクセスログと、フロントWebサーバのアクセスログを比較すればわかりそう。送信元ごとにリクエスト数をグラフ化すれば良い。ユーザからは1リクエストなのに、バックエンドのサーバで1000リクエストがあったら、どこか繰り返し呼び出しをしてる。
    • サービスの粒度が細かく、アクセスログからでは全体の把握が苦しくなったらzipkin使えないか考える。3〜4つのREST APIの連携であればアクセスログ収集と解析でもいけそう。
  • ロギング過多
    • プロファイラ使わずにログからとなると dfによるディスク使用量ぐらい?
  • GC回数、ライブヒープの増加
  • OSリソース系
    • ELK構成でsar, df, proc xxx を取り込む以外にも、RHEL7(および6.7)以上であれば、Performance Co-Pilot + Netflix vectorの選択肢がある。vectorがELKより便利そうかは要調査。

[CON6446] WebSocket in Enterprise Applications

WebSocketの導入セッション。ゲーム以外のWebSocketの使い道をイメージするために聴講。前半はLong polling/SSE/WebSocket/HTTP2の違いについてだったので省略。

WebSocketを何に使うか:
いくつかの例が紹介された。

  • サーバプッシュ
    • 本来はSSEの領域だが、IE11/Edgeが対応していないため。
    • HTTP2のプッシュは、png, css, jsなどのページ構成リソースを、リクエストを待たずに送付するため。唐突にプッシュ通知するためのものではない。
  • 頻繁にリクエスト&レスポンスがある処理。株トレーディングなど。
  • リモートコントロール。リアルタイムでフィードバックを受けながら操作するもの。機械操作など。
  • リアルタイムモニタリング
  • バックエンドサーバ間の高速なリクエスト&レスポンス。
    • メモ: 高速RPC用途であれば、grpcなどの選択肢もあると思う

WebSocketのサブプロトコル:
WebSocket自体は、ブラウザの世界にソケット接続を持ってきたものなので、アプリケーション固有のjson以外にも、サブプロトコルがいくつか出ている。

  • RFC 7118 SIP over WebSocket
  • RFC 7395 XMPP over WebSocket
  • そのほかにも anything over WebSocket が可能

WebSocketのクラスタ化:
複数のサーバに分かれてWebSocket接続する場合は、他のサーバで接続するWebSocketの存在を考慮する必要がある。たとえば一斉通知する場合、Java EEであればJMSやJCacheによってWebSocketのサーバ間連携を実現する。また、サーバサイドでのフェールオーバは難しいため、障害時はクライアントから再接続する。

[CON6856] Saving the Future from the Past

Javaの非推奨(@Deprecated)の歴史と今後について紹介するセッション。
JJUG CCC 2014 Springで講演されていた、オラクルのStuart Marksさんが"Dr. Deprecator"として白衣を着て発表。

Deprecatedの歴史:

  • 歴史は古く、JDK1.1から既に非推奨は存在
  • 当初はJDK1.1で新しく多くのAPIが導入されたために、古いものを捨てる(abandon)ため、代わりのもっと良いもの(supersede)が出ていることを、ユーザに知らせる仕組みとして導入された。
  • ユーザに新しいものに移行したもらうためのものだった。

Deprecatedの意図の多様化:
当初は今後はメンテしない(abandon)、代わりに良いのがある(supersede)がDeprecatedの意図であったが、現在ではもっと多用な意図でDeprecatedが使われている。

  • CONDEMNED: 今後削除される可能性がある
  • DANGEROUS: データが壊れたり、デッドロックが起こる可能性がある。
    • DANGEROUSの代表例はThread.stop()

Deprecatedこそ付いていないものの、Vectorなどの古いAPIは、ユーザにとって"非推奨"と見られている。

Deprecatedに気が付かないパターン:
コンパイル警告、JavaDocIDEの取り消し線でユーザに知らせているが、JDKの移行の仕方によっては気が付かないパターンもある。再コンパイルせずにバイナリ互換を活かしてJDKのバージョンアップをした場合はユーザはわからない。

APIのライフサイクル:
以下のようなフローが理想とされているが、Java SEの実態は異なる。

  1. 新しいAPIが導入され、古いAPIが非推奨になる
    • しかし実態は、多くの人は非推奨警告を無視する
  2. ユーザは新しいAPIに移行する
    • 実態は様々な理由からなかなか移行しない
  3. 移行が済んだころに、APIは削除される
    • 実態は、JDK9となってもほとんどのAPIは削除されていない。

なぜJava SEはAPIを削除しないのか:

  • APIの削除は究極の非互換である
  • ソース互換性はコンパイルが通らず壊れる、バイナリ互換性もNoSuchMethodErrorで壊れる、振る舞い互換性もAPIがないのでもちろん壊れる
  • なぜ無くなったのか、何がなくなったのかjavadoc化が困難

これからのDeprecated:

  • 非推奨の理由カテゴリを再整理し、enum型で表現したい。
    • UNSPECIFIED(特になし)
    • CONDEMNED(将来的に削除予定)
    • DANGEROUS(リスクあり)
    • OBSOLETE(時代遅れで古い)
    • SUPERSEDED(もっと良い代替がある)
    • UNIMPLEMENTED(実装なし、呼ぶとUnSupportedOperationExceptionになるので注意)
@Deprecated(value={DANGEROUS,SUPERSEDED}, replacement="String#getBytes()", since="1.1")
public void getBytes(int srcBegin, int srcEnd, byte[] dst, int dstBegin)
  • ランタイム時にも@Deprecatedの警告を出して、ユーザに通知したい
  • javadocの"All Methods"から@Deprecatedのメソッドを削除して、ユーザに存在を知らせないようにしたい
  • IDEが非推奨APIの利用をもっと自動置換してくれるようになって欲しい
    • しかし、スレッドセーフを意図してHashTableを使っているなど、単純な置き換えはバグを招く

JavaOne 2015 3日目メモ (10/27)

3日目も晴れている。今年は週間天気予報に雨マークが出てたから折りたたみ傘を持ってたが、4回目で今のところ傘が必要なほどの雨に振られたことがない。

今日は以下のセッションに参加。

[CON6712] Enhanced Process APIs

JDK9に向けて、ProcessBuilderの改善アイディアについて。
プロセスの情報をもっとJavaAPIで取得したい:

  • allProcesses()でpsのようにマシン上のプロセス一覧を取得
    • (OSのアクセス制限の範囲で)
  • getCurrent()で現在のプロセス情報、of(pid)で対象pidの情報
  • getPid(), info()のようにプロセス情報の収集
  • onExit()でCoumputableFutureによる非同期ハンドリング?

ProcessHandle.Infoクラスから、対象のプロセス情報を収集する。ユーザ、実行したコマンド、コマンド引数、開始時刻、CPU時間など。取れる情報もあれば、OSの制限により取れない情報もあるので、返り値はOptionalでラップして、例えば Optional user = ph.info().user(); のように返す。

以下の例はプロセスID、親プロセス、ユーザ、コマンドをロギング。

showProcess(ProcessHandle.current());

static void showProcess(ProcessHandler ph) { 
    ProcessHandle.Info info = ph.info();
    log.printf("pid: %d, parent: %s, user: %s, cmd: %s%n",
        ph.getPid(), ph.getParent(), info.user().orElse("none"), info.command().orElse("none"));
}

Java8のストリームAPIへの対応:
以下の例は現在のユーザのプロセス一覧をプロセスID順にソートしてロギングする。

Optional<String> currUser = ProcessHandle.current();
ProcessHandle.allProcesses()
    .filter(p1 -> p1.info().user().equals(currUser))
    .sorted(CodeSamples::parentComparator)
    .forEach(CodeSamples::showProcess);

static int parentComparator(ProcessHandle p1, ProcessHandle p2) {
    return Long.compare(p1.parent().get().getPid(), p2.parent().get().getPid());
}

非同期による外部プロセスの起動:
Java8から入ったComputableFutureをProcess.onExit()メソッドの返り値で返す。
以下の例はプロセスの起動と終了を複数の多重度でプールループで実行している。プロセス終了のタイミングでプロセスID、終了コードのロギングと、再度プロセスの起動を行っている。

Semaphore count = new Semaphore(11);
CountDownLatch end = new CountDownLatch(1);

// paralleism分の多重度でプロセスを繰り返し起動
for (int i = 0; i < paralleism; i++) 
    start(pb1, count, end);

// 並行で実行する全てのプロセスが起動するまでラッチで待機
end.await();

// paralleism分の多重度でプロセスが起動したら、
// Futureと同じような動きをするonExit.get()をプロセスごとに実行して、
// 各プロセスの完了までブロックする
ProcessHandle.current()
    .children().forEach(CodeSample::waitForExit);

static void start(ProcessBuilder pb, Semaphore count, CountDownLatch end) {
    try {
        if (count.tryAcquire()) {
            Process p = pg.start();
            p.onExit()
              .thenAccept(CodeSamples::logExit)
              .thenRun(() -> start(pb, count, end));
        } else {
            end.release();
        }
    } catch (IOException ioe) {
        throw new RuntimeException("Process start failed", joe);
    }
}

static void logExit(Process p) {
    log.printf("exit: %d, status: %d%s", p.getPid(), p.exitValue());
}

static void waitForExit(ProcessHandle p) {
    try { p.onExit().get(); } catch (Exception e) { ... }
}

ProcessBuilderへのパイプの適用:
ls | fgrep dukeと同じようなパイプ繋ぎのコマンドを、Javaで実現する。

ProcessBuilder pb1 = new ProcessBuilder();
ProcessBuilder pb2 = new ProcessBuilder();

List<Process> processes = ProcessBuilder.startPipe(pb1, pb2);
processes.forEach(p -> {
    try {
        int status = p.waitFor();
        log.printf("status: %d%n", status);
    } catch (InterruptedException ie) {
    }
}

他にも今までのProcessはInputStream/OutputStreamを返すが、NIOのChannelに対応させる案の紹介。ノンブロッキングIOに使われているselector()を使って、応答が返ってきたタイミングで通知を受ける案も紹介された。

[CON11284] JDK 9 Language and Tooling Features

Jigsaw以外のJDK9の変更点についてまとめたセッション。
対話式でjavaを実行するjshellの導入、javadocの検索フォームの導入以外はよくわからなかったので要復習。
セッション資料はここで公開されている。

[CON3239] Tuning JavaServer Faces

JSFでパフォーマンスに関する問題を起こさないために、どんなことに注意すべきか紹介するセッション。

JSFの性能にインパクトを与える観点:

  • ページサイズ
  • セッションのサイズ (コンポーネントツリーとアプリケーション分の両方)
  • リクエストパラメータのサイズ
  • EL式の評価
    • EL式のメソッド呼び出しでDBリクエストしない
  • JSFのコンフィグフラグ
    • web.xmlで設定できるチューニングパラメータ 参考
  • FacesContext.getCurrentContext()の繰り返し呼び出し

Bloated component tree対策:
panelタグや、Primefacesなどのコンポーネントにあるタブ機能を使うと、Bloadted component tree (広がりすぎたコンポーネントツリー)に陥りがちである。これは、output系のタグにrendered=false属性を指定しても、画面に表示されないだけで、コンポーネントとしては存在するので性能に影響を与える。以下のような観点で対処を行う。

  • <panel>はdivやspanに置き換えられないか
  • JSF2.2から導入されたステートレスビューの導入
  • 動的ローディングの機能がないか探す
    • たとえばタブであれば、遅延ロード機能が用意されているはず

セッションをなるべく使わない:
セッションの代わりに可能な限り以下を使い、ライブヒープによるGCコスト増加や、レプリケーションコストの増大を避ける。

  • 可能であればリクエストスコープ、Ajaxで更新する画面ならビュースコープ。
  • Flowスコープ(JSF2.2)、Conversationスコープ(CDI)などの期限付きスコープを使う。

id名を短縮してレスポンスデータ量削減:
標準では フォームID:テキスト入力フォームID のような形式でコロン区切りで長いIDが付く。このID名が長い場合は、<h:form id="f" ... のように、1文字IDを明示的に指定すると、テーブルの中にフォームがあるような、何度も繰り返し長い文字列のIDが表示されるケースでデータ量削減に有効。

PartialSubmitを使ってリクエストデータ量削減:
標準の<h:commandButton>は、テーブルの列にフォーム入力がある場合など、大量の変更可能な入力項目の1つを変えただけでも、フォームに含まれるすべてのテキストインプットの内容を再送信する。Primefacesなどのライブラリが用意するコンポーネントを使うと、変更したフォーム内容だけ再送信するPartialSubmit機能がある。
この機能により、リクエスト送信量の削減、送信されたパラメータを再処理するJSFサイクルコストの低減が図れる。

不必要なFacesContext.getCurrentInstance()呼び出しを避ける:
getCurrentInstance()のコストは少なくないため、たとえば同一メソッド内での複数呼び出しは避ける。

[CON2483] Java SE 8 for Java EE Developers

Java EE開発者に有効なJava SE 8の機能を紹介するセッション。

Java EE7でInstantを使う:
JPAの@Convertを使って、DateからData and Time APIのInstantに変換する。Java EE7はDate and Time APIに対応していないため、自作する。

public class DateConverter implements AttributeConverter<Instant, Date> {
    public Date convertToDatabaseColumn(Instant instant) {
        return Date.from(instant);
    }
    public Instant convertToEntityAttribute(Date date) {
        return date.toInstant();
    }
}

以下のようにエンティティクラスのフィールドに適用する。

@Convert(converter=DateConverter.class)
@Temporal(TemporalType.instance);
private Instant instance;

その他もろもろ:

  • StringJoiner使おう
  • ComparatorもComparator.comparingBy(Person::getLastName)のように簡潔に書く
  • MapのcomputIfAbsent、getOrDefaultはEEアプリケーションでもよく使う
  • Files.lines(path)でファイルは読む
  • APサーバ上で間違ってparallelStream()が使われてしまうことへの対策
    • System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "1")

JDK9 JEP269の話:
JEP269がJDK9に盛り込まれれば、EEの世界でもよく使うコレクション初期化がシンプルにできる。

List<String> strints = List.of("one", "two", "three", "four");

[CON2876] JSR 373: New Java EE Management API

Java EE 8に向けて検討が始められている、RESTベースの監視、管理APIの標準化について。結論からまとめると、決まっていることはまだないので、セッション参加者に色々アンケートととって、仕様策定においての悩みをユーザに共有セッション。
フィードバックフォームはここで公開されている。

[BOF7768] Let’s Visualize Log Files for Troubleshooting Java Applications

@cero_tさんと、Koji Ishidaさんのセッション。トラブルシューティングに欠かせないログの可視化について。

冒頭、logをGoogleイメージ検索した、大量の丸太の写真と絵で笑いを取る。海外でも笑いを取りにいく@cero_tさんはすごい、場が暖まる。

内容はホテルの予約サイトの性能、エラー多発トラブルの実例をベースに、どのように問題対処していったかについて。

まずはじめに、seleniumによるテストケース作成と、ソースコードリーディングで問題解析しようとしたが、コード量も多く、困っている問題事象はプロダクション環境でしか起こらなかったため、あまり有効でなかった。

プロダクション環境でしか起こらない問題は、プロダクション環境を対象に解析する必要がある。サービスに影響を与えられないのでログを活用した。ELK(ElasticSearch + Logstash + Kibana)のログ収集の基盤を作り、Apacheアクセスログからリクエスト遅延とMySQLのスロークエリに相関があることを見つけ、性能問題を対処した。

エラーの多発もLogstashで収集したログを、javaプログラムにロードして解析し、原因解析を行った(この部分聞き逃したので少し自信なし)。

ログを解析するにあたって、ElasticSearchでの可視化、cron+javaプログラムでのエラー解析、ファイルサーバに転送してエディタで目視するためには、ログを一度溜め込んで、各ツールに分配するLog Stream Hubが必要となった。今回の事例ではRedisをハブとした。

このようなトラブルログ分析基盤は開発者向けであり、お金が付きにくい。そこでkibanaを販売状況などのビジネスデータの可視化にも利用した。ELK構成はいずれもオープンソースなので、まずは小さくはじめて見ようとセッションを締めくくった。

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がロードされる
  • モジュール間の循環参照はいけない。依存関係のグラフ解決ができない。

JavaOne 2015 1日目メモ (10/25)

昨年よりも約1ヶ月遅いJavaOneが始まった。初日はコミュニティ主体のセッションであるユーザグループフォーラムと、キーノートがサンフランシスコのモスコーンセンタにて行われた。以下、参加メモ。

[UGF11082] WebSocket Perspectives

スピーカーはニューヨークにあるJavaユーザグループNYJavaSIGのリーダFrank Grecoさん。WebSocketはあくまでトランスポートレイヤの仕組みで、WebにTCPを持ち込んだが、そもそもなんでWebにTCPが必要になったかの話。

その背景には、ここ10年で以下のような変化があったこと。

  • WebはページからAPIサービスの塊になり、目に見えないものになった
    • いまやカウント対象はページビューではなく、APIの呼び出し回数
  • RESTで実装されるようになったが、リクエスト/レスポンスモデルは待ちが多い
    • 非同期処理で待ちを軽減させていた。ReactiveXが解決策。
  • マイクロサービスの普及。Webアプリケーションが1つの小さいプロセスの連携であるThe Unix Wayの考え方に近づいている。
    • マイクロサービスのペナルティもリクエスト応答の待ち時間。

WebSocketはあくまでトランスポートレイヤであるため、例えばAMQPをWebSocket上でやり取りすることもできる。リクエスト/レスポンスモデルが主体のWebの世界に、public/subscribeモデルが持って来れる。

実際にWebSocket/AMQPを使ったいくつかのチャット、ゲームのデモアプリケーションを紹介。デモはKAAZING.orgに公開されている。公開されていないデモの紹介として、ドローンのWeb画面によるモニタリングアプリを紹介。高度、進行方向、プロペラ回転数などの状態は常に変化するため、WebSocketだからこそ実現できる。

ここからは感想。今までWebSocketの双方向通信の使い道はゲームぐらいしか思い当たらなかったが、ドローンが監視できることは、企業のシステムでも機械の監視がWebで出来るを思った。例えば、過加熱、消費電力、通信量など、常に変化して、今までは定点間のデータは捨てていた場合、WebSocketが使えるのでは。

[UGF11073] Who Wants to Be a Millionaire? Dealing with Money and Currency in Java Applications

JSR 354 - Currency and Moneyについて。お金を扱うAPIの標準化について。
たびたびJavaOneでも見かけたが、2015/5に仕様策定が完了したとのこと。Java SEをターゲットとした仕様だが、JDK9 or JDK10のいつ盛り込まれるかは未定。

内容はすべてライブコーディングによるAPIのデモ。

mavenのセントラルリポジトリにも既に登録されており、以下の依存性を追加すると実際に参照実装が試せるようになっている。

<dependency>
    <groupId>org.javamoney</groupId>
    <artifactId>moneta</artifactId>
    <version>1.0</version>
</dependency>

お金がintやBigDecimalのような数値でなく、MonetaryAmountとして抽象化されている。MonetaryAmountはイミュータブル。MonetaryAmount.addなどすると、別のオブジェクトが返ってくる。

// 100円
CurrencyUnit yen = Monetary.getCurrency("JPY");
MonetaryAmount yen100  = Money.of(100, yen);

CurrencyUnit yen = Monetary.getCurrency("JPY");
MonetaryAmount yen50  = Money.of(50, yen);
System.out.println(yen100.add(yen50));      //=> JPY 150

// 10.25ドル
CurrencyUnit dollar = Monetary.getCurrency("USD");
MonetaryAmount money  = Money.of(10.25, dollar);

Stream APIを使って通貨ごとのサマリデータを取得することが可能。

CurrencyUnit yen = Monetary.getCurrency("JPY");
MonetaryAmount money  = Money.of(100, yen);
MonetaryAmount money2 = Money.of(200, yen);
MonetaryAmount doller1 = Money.of(10.12, Monetary.getCurrency("USD"));
MonetaryAmount doller2 = Money.of(20.12, Monetary.getCurrency("USD"));
MonetaryAmount euro = Money.of(1, Monetary.getCurrency("EUR"));

List<MonetaryAmount> moneys1 = Arrays.asList(money, money2, doller1, doller2, euro);

// 日本円のサマリデータを取得
Map<CurrencyUnit, MonetarySummaryStatistics> summary  = 
    moneys1.stream().collect(MonetaryFunctions.groupBySummarizingMonetary()).get();

MonetarySummaryStatistics yenSummary = summary.get(yen);
System.out.println(yenSummary.getAverage()); // JPY 150
System.out.println(yenSummary.getSum()); // JPY 300 
System.out.println(yenSummary.getMax()); // JPY 200
System.out.println(yenSummary.getMin()); // JPY 100

為替情報も取得でき、異なる通貨値の変換ができる。wifiを落とすと例外がスローされたので、RIでは最新レート情報をWeb上に取得に行っているようだ。

// 為替レートの取得
ExchangeRateProvider exchangeRateProvider = MonetaryConversions.getExchangeRateProvider("IMF");
ExchangeRate rate = exchangeRateProvider.getExchangeRate("USD", "JPY");
 System.out.println(rate.getFactor());  // => 120.93957... (2015/10/25のドル->円レート)

// 1ドルを日本円に変換
CurrencyConversion dollarConversion = MonetaryConversions.getConversion("JPY");
MonetaryAmount oneDollar = Money.of(1, "USD");
System.out.println(oneDollar.with(dollarConversion));  // => JPY 120.9395779...

いくつかのサンプルコードがgithubjavamoney-examplesで公開されている。
複数の通貨を扱うアプリケーションでは、いままでBigDecimalBigDecimalの数値が表す通貨単位情報を1つのクラスにラップしていたと思うので、このAPIを使うとシンプルにコードが扱えるのでは。日本円だけを計算する場合の有効性はもう少し調べてみる必要がある。

Java Keynote

いろいろな所で紹介されると思うので詳細は省略。
jigsawが何を解決させるかについて、以下の3点を掲げていたのは去年のJavaOneでは説明されていなかったと思う。

  • Is anything missing?
    • NoClassDefFoundErrorが出ても、クラスパスに含まれる大量のjarから、どのjarが漏れてるかわからない
  • Are the any conflicts?
    • maven使っても、commons-binutil-1.7.0と1.8.0の両方が依存性に含まれることがある。別バージョンの競合
  • Is it safe to change internal API?
    • アプリ内の他パッケージに見せるためにpublicにしたのに、意図しないアプリ外から参照される。内部APIでも容易に変更できない。

classpathは依存関係が定義できないのが弱点。moduleは新しいグループの単位で、module->package->class->filed,method の順に位置付けされる。依存性の定義は、module-info.javaに定義される。

module com.greetings {
    // このモジュールが依存するモジュール
    requires test.common;
    // 他のモジュールからも参照されても良いパッケージ
    exports com.greetings.api;
}

jigsawの具体的な使い方は明日以降のセッションで説明があるとのこと。

WeldのProxyクラスはどこからやってくるか

CDIの参照実装 Weld では @Dependent を除いて、全てのCDI管理Beanは直接インスタンスがインジェクションされずに、以下のようにプロキシが設定*1される。

test.Message$Proxy$_$$_WeldClientProxy

例外発生時のスタックトレースや、スレッドダンプでも見かける $Proxy$_$$_WeldClientProxyだが、どこからやってきているのか調べてみた。

疑問に思っていた事

Javaで一般的にプロキシといったら、以下のような手段が取られると思う。

  • java.util.reflect.Proxy によるプロキシ実装
  • objenesiscglibなどのライブラリ

JDK付属プロキシでは、インタフェースに対してプロキシを設定するため、CDIのようにインタフェースなしのクラスに対してプロキシを設定できず、利用できない。また、Weldのpom.xmlを見ても、objenesisやcglibへの依存が見られないため、ライブラリも使っていないようである。

WeldのProxyクラスは一体どこからやってくるのか?

WeldのProxyの生成はどうやっているか

org.jboss.weld.bean.proxy.ProxyFactoryで、プロキシの生成は行われている。
大まかな処理の流れは以下の通り。

  1. ProxyFactory.create(BeanInstance beanInstance)
  2. ProxyFactory.run()
    • java.security.PrivilegedActionの実装。この後の処理でリフレクションAPIを実行するため、java.security.*によるアクセス制御対象になっている。
  3. ProxyFactory.getProxyClass()
  4. ProxyFactory.getProxyClass()
    • 対象のプロキシクラスが既にクラスローダにロード済みであれば、newInstanceメソッドで生成。
    • 未ロードされていない場合は、createProxyClass()を呼び出して、動的にプロキシクラスの生成とロードをする
  5. ProxyFactory.createProxyClass()

Proxyの生成に関連するところを太字にしたが、Weldは外部OSSライブラリに頼らずに、JBossの関連モジュールのみで、プロキシクラスの動的なバイトコード生成と、クラスローダ経由でのロードを行っている。

org.jboss.classfilewriter.ClassFileを見ると、以下のようにクラスファイル先頭に出てくるCAFEBABEの出力が見られ、バイトコード作ってる感がある。

205        stream.writeInt(0xCAFEBABE);// magic
206        stream.writeInt(version);
207        constPool.write(stream);

Weldの場合、バイトコードの生成とクラスロードが別々のクラスに分かれているが、やっていることはobjenesisのClassDefinitionUtilsクラスと似たように見える。objenesisでも動的なバイトコード生成と、ClassLoader.defineClassメソッドの呼び出しを行っている。

まとめ

  • WeldのClientProxyは、$Proxy$_$$_WeldClientProxyを接尾子としたプロキシクラスのバイトコード生成とクラスロードをjava.util.reflect.Proxyや外部OSSライブラリを利用せずに独自に実装している。
  • org.jboss.weld.bean.proxy.ProxyFactoryから追いかけていくと流れがよくわかる。

*1:Bean間の循環参照を許容するため、異なるスコープを持ったBeanの参照依存関係を分離するため

JSF (mojarra) ステートレスビューを試してみる

JSF2.2のビッグチケットとして盛り込まれたステートレスビューですが、HttpSessionへのアクセス量を減らせるメリットの一方、制約事項もあります。Wildfly-9.0.0.CR2(mojarra-2.2.11)で色々と試してみたので以下にまとめます。

ステートレスビューの使い方

<f:view transient="true">で囲うだけで、対象のビューのフォーム値などの状態をセッションに保持しなくなります。

<html xmlns="http://www.w3.org/1999/xhtml" 
  xmlns:f="http://java.sun.com/jsf/core"
  xmlns:h="http://java.sun.com/jsf/html">
  <f:view transient="true">
    <h:head>
      <title>JSF Hello World</title>
    </h:head>
    <h:body>
      <h1>JSF Hello World</h1>
      <h:form>
       ...
      </h:form>
    </h:body>
  </f:view>
</html>

いつから使える?

仕様としてはJSF2.2の新機能としてJAVASERVERFACES_SPEC_PUBLIC-1055として作成されましたが、JSFのRIであるmojarraではJAVASERVERFACES-2731の修正により、Java EE6世代の2.1系である2.1.19から使えるようになっています。

ステートレスビューのメリット

一般的には以下のようなメリットが得られると言われています。

  • レスポンスタイム向上
  • スケール性の向上・ヒープ使用量の低減

手元のHelloWorldアプリでは違いがよくわかなかったですが、スループット・レスポンスタイム向上度については、アプリケーションにも依存すると思うので、既存のアプリに<f:view transient="true">を入れて測ってみるのがおすすめです。

mojarra-2.2.11の場合、シリアライズされたビュー状態をcom.sun.faces.renderkit.ServerSideStateHelper.LogicalViewMapという属性名でセッションに登録しているようですが、ステートレスビューにするとLogicalViewMapへの登録がなくなりました。あくまで仮説ですが、大規模にセッションレプリケーションしている環境では、HttpSessionへのアクセスが減ることでシステム全体のスループットにも好影響があるのかと思います。

ステートレスビューのデメリット

一方で、以下のようなデメリットもあります。

  • ViewScopeが使えない
  • CSRF対策が必要
ViewScopeが使えない

JSFAjax機能を使う時によく使う、同一ビューへのリクエストであれば値を保持し続けるViewScopeが使えません。Primefacesなどの内部的にAjaxが使われているコンポーネント利用時にも注意が必要と思います。ステートレスビューとViewScopeを組み合わせると、アクセス時に以下のような警告ログが出力され、ViewScopeが設定されていてもRequestScopeと同様に振る舞います。

2015-06-21 20:27:23,737 WARNING [com.sun.faces.application.view.ViewScopeManager] (default task-2) @ViewScoped beans are not supported on stateless views

JSF2.2においてCDIでもViewScopeを定義できるアノテーションが導入されていますが、いずれの場合でもステートレスビューと組み合わせることはできません。

  • javax.faces.bean.ViewScoped (JSF2.0導入, JSFの@ManagedBeanと使う)
  • javax.faces.view.ViewScoped (JSF2.2導入, CDIの@Namedと使う)
CSRF対策が必要

今までのステートフルビューでは、hiddenフィールドのjavax.faces.ViewStateが暗黙的なCSRF対策トークンとして機能しており、この正しいID値をPOSTしないとビューが復元できず、業務ロジックが実行される前のRESTORE VIEWフェーズで例外となっていました。

<input type="hidden" name="javax.faces.ViewState" id="j_id1:javax.faces.ViewState:0" value="1867897074285420267:8829522400179303592" autocomplete="off" />

ステートレスビューでは、ViewStateに以下のように固定的な文字列statelessが割り当てられる為、CSRF対策トークンとして機能しません。

<input type="hidden" name="javax.faces.ViewState" id="j_id1:javax.faces.ViewState:0" value="stateless" autocomplete="off">

JSF2.2から盛り込まれた明示的なCSRF対策トークン付与機能によって以下のように設定することもできますが、CSRF対策したい部分(購入完了、決済完了などDB登録処理)のビューを個別に指定する手間は掛かります。JSFのフォームPOST先のURLは、現在表示しているViewであることを意識しながら設定するのはそれなりに大変です。

WEB-INF/faces-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<faces-config xmlns="http://xmlns.jcp.org/xml/ns/javaee"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd"
              version="2.2">
    <protected-views>
        <url-pattern>/inputform.xhtml</url-pattern>
    </protected-views>
</faces-config>

この設定を加えてinputform.xhtmlにフォワードさせると、以下のようにformのaction属性の値にトークンが付与されます。このトークンの値がない/間違ってる状態でHTTP POSTされると、javax.faces.application.ProtectedViewExceptionがスローされ、RESTORE VIEWフェーズの途中で処理が終了します。

<form id="j_idt7" name="j_idt7" method="post" action="/jsftest/input.xhtml?javax.faces.Token=i0hAEnknIkohc0GnhQ%3D%3D" enctype="application/x-www-form-urlencoded">

まとめ

  • mojarra2.1.19(EE6系)および2.2.0-m10(EE7系)より、ステートレスビューが利用可
  • JSFステートレスビューには制約事項もある
    • ViewScopeが使えないので、Ajax機能が使いにくい
    • javax.faces.ViewStateによるCSRF対策ができないため、別の対策が必要
  • 制約事項を考えると、特に困ってないなら無理にステートレスビューを使う必要はないかと思う。遅いビューに部分的に使いたい。

JSF (mojarra2.x) のデバッグログ出力

多くのアプリケーションのログカテゴリには、ログを出力しているパッケージ名(jp.co.test... など)が使われていますが、JSFの参照実装であるmojarra2.xでは少し異なる付与規則となっているため、以下にまとめます。

mojarraのログカテゴリ定義

mojarraのログカテゴリはjavax.enterprise.resource.webcontainer.jsfから始まります。mojarraの実装パッケージはcom.sun.facesから始まっていますが、com.sun.facesをログカテゴリに設定してログレベルを上げても何も出力されません。

カテゴリの一覧はソースコード上のcom.sun.faces.util.FacesLoggerenumとして定義されています。以下のようなカテゴリ定義があります。

  • javax.enterprise.resource.webcontainer.jsf.application
  • javax.enterprise.resource.webcontainer.jsf.resource
  • javax.enterprise.resource.webcontainer.jsf.config
  • javax.enterprise.resource.webcontainer.jsf.context
  • javax.enterprise.resource.webcontainer.jsf.facelets
  • javax.enterprise.resource.webcontainer.jsf.lifecycle
  • javax.enterprise.resource.webcontainer.jsf.managedbean
  • javax.enterprise.resource.webcontainer.jsf.renderkit
  • javax.enterprise.resource.webcontainer.jsf.taglib
  • javax.enterprise.resource.webcontainer.jsf.timing

JSFライフサイクルのロギング

javax.enterprise.resource.webcontainer.jsf.lifecycleカテゴリのログレベルをFINE以上に変更すると、JSFライフサイクル(Restore View -> Apply Request Values ..)の開始・終了時間のロギングが可能です。PhaseListenerによるロギングのようにアプリケーション自体をリビルドしなくても、APサーバの設定変更のみでログ出力が可能です。

ログ出力例

WildFly8.2.0でjavax.enterprise.resource.webcontainer.jsf.lifecycleのログレベルをDEBUGに変更すると、以下のようなログが出力されます。

2015-06-14 16:31:34,393 FINE  [javax.enterprise.resource.webcontainer.jsf.lifecycle] (default task-2) execute(com.sun.faces.context.FacesContextImpl@5f6d3a9e)
2015-06-14 16:31:34,393 FINE  [javax.enterprise.resource.webcontainer.jsf.lifecycle] (default task-2) Entering RestoreViewPhase
2015-06-14 16:31:34,417 FINE  [javax.enterprise.resource.webcontainer.jsf.lifecycle] (default task-2) Postback: restored view for /index.xhtml
2015-06-14 16:31:34,417 FINE  [javax.enterprise.resource.webcontainer.jsf.lifecycle] (default task-2) Exiting RestoreViewPhase
2015-06-14 16:31:34,418 FINE  [javax.enterprise.resource.webcontainer.jsf.lifecycle] (default task-2) Entering ApplyRequestValuesPhase
2015-06-14 16:31:34,419 FINE  [javax.enterprise.resource.webcontainer.jsf.lifecycle] (default task-2) Exiting ApplyRequestValuesPhase
...

長くなるため完全な出力例は省略しますが、ログレベルさらに上げると、RenderResponsePhaseのロギングとして、以下のようにUIViewRootをルートとしたコンポーネントツリーの状態をロギングすることもできます。

2015-06-14 16:31:30,817 FINEST [javax.enterprise.resource.webcontainer.jsf.lifecycle] (default task-1) +=+=+=+=+=+= View structure printout for /index.xhtml
2015-06-14 16:31:30,822 FINEST [javax.enterprise.resource.webcontainer.jsf.lifecycle] (default task-1) id:j_id1
type:javax.faces.component.UIViewRoot
...
APサーバ毎のログレベル設定方法

GlassFish4の場合の設定方法
mojarraのログカテゴリは、デフォルトのlogging.propertiesに既に定義されているため、デフォルトのINFOから好きなログレベルに変更するだけです。ログレベルの表記はSEVEREからFINESTまでのjava.util.logging形式です。一覧はLevelクラスのjavadocに定義されています。

glassfish4/glassfish/domains/domain1/config/logging.properties

javax.enterprise.resource.webcontainer.jsf.lifecycle=FINE

WildFly8 (JBoss) の場合の設定方法
CLIまたは、standalone.xmlを編集してログカテゴリ設定を追加します。java.util.loggingとJBoss Loggingのログレベルの対応表は、jbossのwikiに記載があります。

CLIの場合

${WILDFLY_HOME}/bin/jboss-cli.sh -c
/subsystem=logging/logger=javax.enterprise.resource.webcontainer.jsf.lifecycle:add(level=DEBUG)

standalone.xmlを編集する場合 (jacorbの下に追加)

<logger category="jacorb.config">
    <level name="ERROR"/>
</logger>
<logger category="javax.enterprise.resource.webcontainer.jsf.lifecycle">
    <level name="DEBUG"/>
</logger>
<root-logger>

まとめ

  • mojarra2.xのログカテゴリはcon.sun.facesのような実装パッケージ名でなく、javax.enterprise.resource.webcontainer.jsfから始まる。
  • この仕組みを使ってJSFライフサイクルのデバッグができる