大混乱に陥っているJavaEE 6のアノテーションに関する使い分けについて

JavaEE 6標準は、従来のJavaEE 5に対してさらなるEoDと軽量化を目指しているということだったのですが、いざ使おうと思うと、名前が同じアノテーションが複数のパッケージに定義されていたり、意味的にほとんど違いがないようなアノテーションが存在していたりで、初心者はもちろんのこと、ある程度ベテラン開発者であってもどう使い分けてよいものか途方にくれてしまいます。仕様は多数の人間の決めることですから、ある程度機能的な重複や矛盾があることは避けられないことですが、それでも、特にDIやEJB、管理Bean関係のAPIに対する機能重複についてはかつて経験したことがない程ひどく、これで本当にfinalの状態なのかと信じられないくらいです。さまざまなアイデアを取り込むのは大歓迎ですが、かつて仕様に統一感があった(仕様自体はまったく使い物になりませんでしたが)EJB2.1のころの時代が逆に懐かしく感じたりします。以下のような参考サイトの情報をもとに、問題と解決策をまとめてみたいと思います。
http://www.germanescobar.net/2010/04/4-areas-of-possible-confusion-in-jee6.html
http://weblogs.java.net/blog/cayhorstmann/archive/2009/12/23/javaxfacesbeanmanagedbean-dead-arrival
http://forums.java.net/jive/thread.jspa?threadID=153157

1.シングルトンの定義方法

Beanのシングルトン的な振る舞いを定義するアノテーションとしては、

  • @javax.ejb.Singleton(JSR-318、EJB3.1仕様)
  • @javax.inject.Singleton(JSR-330、DI仕様)
  • @javax.enterprise.inject.ApplicationScoped(JSR-299、CDI仕様)
  • @javax.faces.bean.ApplicationScoped (JSR-314、JSF2.0仕様)

の4種類も存在します。まず、@javax.ejb.SingletonはEJBパッケージに属していることからもわかるように、シングルトンBeanと呼ばれる新しいタイプのセッションBeanを作成するときに使うもので、EJBコンテナーのトランザクションや同時実行制御の機能を使うなら、このアノテーションを利用する必要があります。これらのEJBの機能が不要で単にシングルトンを定義したいのであれば、それ以外のアノテーションを選択して利用することになるのですが、この選択基準が非常に難しいです。まず、CDIが利用可能な環境(通常のJavaEE 6コンテナーならサポートされる)であれば、@javax.enterprise.inject.ApplicationScopedを利用するのが無難であると思います。@javax.faces.bean.ApplicationScopedの方はtomcat+Springなどの環境でCDIを利用しない場合にWeb層でシングルトン的な管理Beanを定義する際には利用できます。@javax.inject.SingletonはCDI環境では完全に無視されているわけではなく、擬似スコープの一種として一応利用されていますが、以下のWeldのマニュアルの記述を見るといかにも苦しいところです。
http://docs.jboss.org/weld/reference/1.0.1-Final/en-US/html/scopescontexts.html#d0e1921
また、@javax.inject.Singletonの方は(Rod Johnsonがスペックリードだったにもかかわらず)、今のところSpring Frameworkでも無視されているようです。(https://jira.springsource.org/browse/SPR-6465?page=com.atlassian.streams.streams-jira-plugin:activity-stream-issue-tab)
そういうことで、Google Guiceのファンの方々には申し訳ありませんが、あまり使い道がないというか、ほとんど@Deprecatedなのではと思います。実際、@javax.ejb.Singletonと単純名がかぶるため非常に勘違いしやすいし、@Scopeメタアノテーションの例として、抽象性の低い@Singletonがどうしてjavax.injectパッケージに入っていなくてはならないのかも疑問です。

2.管理Beanの定義方法

EJB以外の管理Beanを定義するための方法は

  • @javax.annotation.ManagedBean(JSR-250、JSR-316)
  • @javax.faces.bean.ManagedBean(JSR-314、JSF2.0仕様)
  • CDIを使って管理Beanを定義する方法(JSR-299、CDI仕様)

の3通りが存在します。まず、一番厄介者だと思うのが、最初の@javax.annotation.ManagedBeanでしょうか。従来管理Beanという言葉はJSFの世界で使われてきたと思うので、2番目のアノテーションと混同しがちですが、@javax.annotation.ManagedBeanはJSFの管理Beanとは無関係であり、どちらかというとEJBの仲間というか、サービス層で利用するアノテーションのようです。EJB同様、@PostContractや@Resourceなどのアノテーションが利用でき、また、JNDI上に登録されます。さらに、EJB同様インターセプターも利用できます。DAOクラスなどセッションBeanを使うまでもないが、コンテナーの機能も使いたいというような場合にはたしかに使えそうです。@javax.annotation.ManagedBean最大の問題点はEL式と互換性がないことで、当然画面からELで直接参照できないため、その紛らわしい名称にもかかわらずJSFの管理Beanとしては利用できないということです。3番目のCDIを使う方法は、beans.xmlさえ定義しておけばほとんど任意のPOJOが管理Beanになるわけですから、CDIが使える環境では他の機構は不要に思えます。ただ、難しいのはCDI万能主義は危険なところもあるかなという懸念ですね。Seamもそうですが、単純でモノリシックな構造のWebアプリケーションであれば、CDIのモデルは有用(CDIはもともとWebBeansと呼ばれていた)ですが、サービス層をきちんとレイヤーに分割したり、分散化したりすることが必要なアプリケーションではコンポーネント間の依存関係がコンテキストを通して暗黙的に結合するため適さない気がします。そのようなケースではCDIを利用しないかWeb層のみに限定して利用し、サービス層で@javax.annotation.ManagedBeanを使うという考え方もないことはないかもしれません。
なお、CDIを使う場合はEJBを直接JSFの管理Beanとして利用できますが、JSF2.0の管理Beanではクラスを分ける必要があります。

3.スコープに関するアノテーション

  • @javax.faces.bean.RequestScopedと@javax.enterprise.context.RequestScoped
  • @javax.faces.bean.SessionScopedと@javax.enterprise.context.SessionScoped
  • @javax.faces.bean.ApplicationScopedと@javax.enterprise.context.ApplicationScoped

などは明らかに重複していますが、管理Beanの定義にCDIを使うか、JSF2.0仕様の管理Beanを使うかで正しく使い分ける必要があります。ただ、問題は両者の機能が完全に一致していないことです。CDIには@ConversationScoped(SeamのConversationスコープに近いが短期状態の振る舞いが少し違いリダイレクト時に残らない)がありますが、JSF2.0には@ViewScoped(Seam2のPageスコープに相当)と@CustomScopedがあるということです。

4.インジェクションに関するアノテーション

EJBのインジェクションについては従来からある@EJBと@Injectが使えます。同様に@javax.annotation.ManagedBeanで定義された管理Beanに対しては@Resourceか@Injectが使えます。基本的にCDIを利用するなら@Injectに統一するべきかと思います。@EJBの唯一の用途はリモートインターフェースをインジェクションする場合くらいでしょうか。ただ、この場合もCDI環境では@Producesをつけたフィールドなどを使って@EJBの利用は一箇所にまとめ、他の部分からは@Injectで参照するのがよいと思います。
このように、今のところJavaEE 6のAPIは統一感がまったくなく、さまざまなOSSから引き継いだ概念の寄せ集めになっている感じがします。すべてCDIに統一せよというCDI帝国主義に陥るのもどうかと思いますが、プロファイルごとに使用可能なAPIを分けるとか、使い方のガイドラインを提示してもらいたいものです。