タグ「Java EE」が付けられているもの

Puzzle : EJB Timer #2

この記事は Java Puzzlers Advent Calendar の 18 日目です。昨日同様、EJB タイマーに関する問題を続けて出題します。

問題

次のような EJB タイマーを含む Web アプリケーションを、Java EE 7 Full Platform の環境にデプロイしました。想定される動作は以下のどれでしょうか?

@Stateful
public SomeTimeBean {
  @Schedule(minute = "10", persistent = false)
  public void execute() {
    System.out.println("Just a time!");
  }
}

(1) 毎時 10 分に "Just a time!" という文字列を出力する

(2) 毎日 0 時 10 分に "Just a time!" という文字列を出力する

(3) デプロイに失敗する

解答

(3) デプロイに失敗する

解説

昨日の問題は EJB タイマーを構築する @Schedule アノテーションのパラメータ誤りでした。本日の問題は、@Schedule アノテーションに誤りはありませんし、永続型の EJB タイマーもデプロイ可能な Java EE 7 Full Platform が前提です。

問題は別のところにあります。EJB タイマーの適用範囲は Stateless Bean もしくは Singleton Bean に限定されます。適用外の Stateful Bean に対して @Schedule アノテーションが付加された場合には、デプロイすることができません。これは EJB タイマーの仕様です。

この問題では、クラスに対して @Stateful アノテーションが付加されており、Stateful Bean であることが宣言されています。その上で @Schedule アノテーションを用いて EJB タイマーを宣言しているため、この EJB 自体がデプロイできないのです。

修正方法は、可能であれば @Stateful アノテーションを @Stateless アノテーションまたは @Singleton アノテーションに置き換えて、Stateless Bean または Singleton Bean とします。

  • EJB がフィールドを持たない場合には、@Stateless アノテーションに置き換えて Stateless Bean にすれば問題ありません。
  • EJB がフィールドを持つ場合で、かつ EJB のインスタンスが 1 つで足りるようであれば、@Singleton アノテーションに置き換えて Singleton Bean とすることにより問題を回避できます。
  • EJB がフィールドを持ち、かつ EJB のインスタンスが複数必要となる場合は、Stateful Bean として維持しなければならないため、残念ですが EJB タイマーを諦めることになります。

3 種類の Session Bean の利用頻度を考慮すると、大半が Stateless Bean になるため、問題になることは少ないとは思いますが、@Schedule アノテーションのにわか知識だけで EJB タイマーを使おうとすると以外とハマりやすい落とし穴です。Java EE の勉強会のスライドで EJB タイマーに触れたものは、Stateful Bean が適用外であることを明示していないものがそれなりにあるため (多分、当日のプレゼンでは口頭で説明しているはず...)、初学者はなおさら注意が必要です。

以上の修正を行った場合は、昨日の修正後と同じ挙動を示します。すなわち、

この場合、想定される動作は選択肢 (2)、すなわち、毎日 0 時 10 分に "Just a time!" という文字列を出力します。@Schedule のパラメータの既定値は、日付部である year、month、dayOfMonth、dayOfWeek についてはすべてワイルドカード "*" となっていますが、時刻部である hour、minute、second についてはすべて "0" となっています。

選択肢 (1) のように毎時 10 分に "Just a time!" という文字列を出力したい場合には、以下のように hour をワイルドカード指定する必要があります。

なお、余談になりますが、Stateless Bean であってもフィールドを持たせること自体は可能です。ただし、その内容は保証されません。これは 1 つのセッションに対して常に同じインスタンスが lookup される Stateful Bean と異なり、Session Bean はプーリングされたインスタンスの中から毎回 lookup し直すため、必ずしも同じインスタンスになるとは限らないためです (インスタンスが異なるとフィールド値も異なる可能性が高く、フィールドが値を保持する意味がなくなります)。文献によっては毎回異なるインスタンスが lookup されるとありますが、それも誤った記述です。正確には、どのインスタンスが lookup されるか保証されません。多くの場合では異なるインスタンスが lookup されますが、同一のインスタンスが lookup される可能性もあるのです。具体例を挙げると、サンプルプログラムなどで Stateless Bean のプールにインスタンスが 1 つしか存在しない場合などは、毎回同じインスタンスを lookup することになります。また、実運用であっても、直前に lookup したインスタンスを再び lookup する可能性もゼロではありません。

これは昨日の問題にも共通して言えることですが、Java EE サーバーの Deployer が発するエラーメッセージは不親切なものが多く、ログに出力されたエラーメッセージだけでは何が間違っているのか分からないことが多々あります。かといって Java EE の文献は公式ドキュメントを含めてザルであることが多いため、最終的には「自分だけを信じろ、他人を信じるな」という結論に達します。今後も共に精進しましょう!

Puzzle : EJB Timer #1

この記事は Java Puzzlers Advent Calendar の 17 日目です。今日、明日と続けて EJB タイマーに関する基本的かつわかりにくい仕様について出題します。

問題

次のような EJB タイマーを含む Web アプリケーションを、Java EE 7 Web Profile の環境にデプロイしました。想定される動作は以下のどれでしょうか?

@Stateless
public SomeTimeBean {
  @Schedule(minute = "10")
  public void execute() {
    System.out.println("Just a time!");
  }
}

(1) 毎時 10 分に "Just a time!" という文字列を出力する

(2) 毎日 0 時 10 分に "Just a time!" という文字列を出力する

(3) デプロイに失敗する

解答

(3) デプロイに失敗する

解説

Java EE 7 より Web Profile でも EJB タイマーを使用できるようになりましたが、非永続型のものに限定されます。@Schedule アノテーションでは永続化/非永続化を persistent パラメータで指定しますが、既定値 (省略時) は true = 永続化です。永続型の EJB タイマーは Web Profile ではサポートされず、デプロイを試みても失敗します。

EJB タイマーは、スケジュールを保持するために何らかの永続化ストアを必要としていました。@Schedule アノテーションは Java EE 6 で EJB タイマーの実装を容易にするために導入され、Unix/Linux の cron に似た直感的で分かりやすい設定を可能にしましたが、Java EE 6 では EJB タイマーはまだ Full Platform 限定でした。

Java EE 7 では EJB タイマーにスケジュールを保持しない代わりに永続化ストアも必要としない非永続型を新たに追加して、それを EJB Lite の仕様に含めました。EJB Lite は Web Profile がサポートする EJB のサブセット仕様です。従来通りに永続化ストアを必要とする永続型は EJB Lite には含まれません (Java EE 7 でも Full Platform に限定されます)。

Java EE 6 で導入された時の @Schedule アノテーションには、persistent パラメータが存在していません。Java EE 7 の API ドキュメントには persistent パラメータの導入時期 (Javadoc の @since) が記載されていないため見落としがちですが、2 つのバージョンを見比べてみるとわかります。

ここまで行わないと追加された機能が分からない API ドキュメントは、そもそもドキュメントとして失格だと思うのですが...

このあたりの事情を師匠から公式の場で伺った覚えがないのと、私を含む多くの開発者が自身の開発環境で Full Platform を使用している (と思われる) ことから、意外な落とし穴となっています。

さて、問題を非永続型の EJB タイマーにするには、@Schedule アノテーションで persistent = false を指定します。

@Stateless
public SomeTimeBean {
  @Schedule(minute = "10", persistent = false)
  public void execute() {
    System.out.println("Just a time!");
  }
}

この場合、想定される動作は選択肢 (2)、すなわち、毎日 0 時 10 分に "Just a time!" という文字列を出力します。@Schedule のパラメータの既定値は、日付部である year、month、dayOfMonth、dayOfWeek についてはすべてワイルドカード "*" となっていますが、時刻部である hour、minute、second についてはすべて "0" となっています。

選択肢 (1) のように毎時 10 分に "Just a time!" という文字列を出力したい場合には、以下のように hour をワイルドカード指定する必要があります。

@Stateless
public SomeTimeBean {
  @Schedule(hour = "*", minute = "10", persistent = false)
  public void execute() {
    System.out.println("Just a time!");
  }
}

Puzzle : JAX-WS and CDI

このエントリは Java Puzzlers Advent Calendar 2016 の 13 日目です。誰も投稿しなかったので、緊急で投稿します。

問題

以下の JAX-WS エンドポイントを WAR ファイルに含めてデプロイしました。

import javax.jws.*;

@WebService
public class Sample {
  
  private String message;
  
  @WebMethod
  public String sayHello(String name) {
    return "Hello, " + name;
  }
}

このエンドポイントに @Inject で DI を行うよう改修を試みましたが、上手くできませんでした。追加の手順として適切なものはどれでしょうか?

(1) クラスに @Stateless を付加する

(2) クラスに @RequestScoped、@SessionScoped または @ApplicationScoped のいずれかを付加する

(3) WEB-INF 以下に中身が空の beans.xml ファイルを配置する

(4) WEB-INF 以下に bean-discovery-mode を適切に設定した beans.xml ファイルを配置する

解答

(3) WEB-INF 以下に中身が空の beans.xml ファイルを配置する

解説

各選択肢について、それぞれどのようになるのかを説明します。

(1) - @Stateless を付けた時点で、JAX-WS のエンドポイントは EJB となります。EJB のほとんどは WAR ファイルに含めることが可能ですが、JAX-WS のエンドポイントとなる EJB に関しては WAR ファイルには含められず、EAR ファイルによるデプロイが必要です。問題の前提条件が WAR ファイルによるデプロイですので、この選択肢を採った場合はそもそもデプロイできないという現象に陥ります。

(2) - スコープ・アノテーションを使用しただけでは、JAX-WS のエンドポイントは @Inject を認識しません。JAX-WS は Java EE 6 から仕様が変更されておらず、CDI 1.0 にしか対応していないためです。

(3) - WEB-INF 以下に空の beans.xml を配置することで CDI (CDI 1.0) が有効になります。これにより、JAX-WS のエンドポイント内でも @Inject が認識するようになります。

(4) - bean-discovery-mode は CDI 1.1 の beans.xml で追加された属性です。設定値によらず、この属性を持つ beans.xml を配置した時点で CDI 1.1 (またはそれ以降) が有効になります。(2) の説明でも挙げたように、JAX-WS は CDI 1.0 しか認識しないため、CDI 1.1 (またはそれ以降) が有効になった時点で @Inject を認識することができなくなるのです。

参考

JJUG CCC 2016 Spring で発表しました

5 月 21 日に開催された JJUG CCC 2016 Spring で Java EE の Web Profile について発表してきました。恒例の資料はこちらです。

今回は聴講してくださったなかやまさんがセッションのメモをイラストにまとめてくださいました。ありがとうございます!(なので今回は解説なしです)

なかやまさんのイラストにある、ハスヌマの横の女の子は誰だ?というツッコミが Twitter のどこかであったような気がしますが、待ち時間用のスライドに使っていた多田李衣菜だと思われます...