Enterprise Java Bean 3.0 (EJB 3)の仕様は、エンタプライズでのJavaの長いマーチにおいて、非常に重要な中間地点となった。仕様が、コミュニティからのインプットで作り上げられたのは、非常に明白である。更に一貫性のあるサービスパラダイムを示しており、もっとPOJOフレンドリで、一般的に複雑でなくなっている。Java 5のアノテーションによってもたらされた間接のレベルが、このパラダイムをもっと強力にし、同時に開発者の労力を減らした。悪くて古い決定を捨てて、違う新しい解決を求める意欲によって、そのフレームワークは、以前、EJBを敬遠した人々に興味深いものになった。EJB Entity Beansがなくなり、JPA Entitiesに替った。EJB 2.1やそれ以前では、平均的なbeanに必要なJavaのクラスやinterfaceが手に余るほどあったが、2つぐらいで済むようになった。「規約は設定に勝る(Convention-over-configuration)」ベースのデフォルト値の実現で、起動して、走らせるのが、もっと簡単になった。EJB 3.0は、革命だった。
EJB 3.0が革命なら、EJB 3.1は、非常に使える、かつありがたい進化である。EJB 3.0にあればよかった、と思わせるたくさんのフィーチャがあるのが、EJB 3.1のフィーチャである。仕様のゆっくりしたペースは、許せることである-不完全なモデルをほとんど進化しない仕様に凍結するより、80%の達成率のほうがマシである。もちろん、その完成を歓迎しないわけがない。-これらの全ての新しいフィーチャでさえ-EJB 3.1仕様、すべての後方互換性そして更に加わったもの-で626ページであり、訳10年前のEJB 2.1より14ページ少ない。
この投稿で、これらのフィーチャのいくつかをレビュ-し、その利用法を考える。
EJB 3.1の新フィーチャ
EJB 3.1でのいくつかの大きな変更は、プラットフォームへの追加ではなく、プラットフォームを使う際に、必要な決まりきったことを減らしたことである。柔軟性を高めるために、ユーザインターフェースAPIの表面積を増したものもある。単に、柔軟性をもたらしたものもある。
シングルトン(Singleton)
シングルトン(singleton)は、1つ余分な保証をする、新しいタイプのセッションbeanである:beanは、動いているJVM1つにつき、1つしか生成されない。このフィーチャで、よりよいサービスが可能になるユースケースがたくさんある:例えば、キャシュ。アプリケーションサーバがまだ提供していないリソースへの共通のビューを保証する能力が、別の例である。永続的に保存される必要のない、しかし再生成するとなると高価な、貴重なデータ用の単純な保管場所が、別のオプションである。(わざとらしい)例を見てみよう。User
エンティティがすでに他の場所で(おそらくJPA 2.0を使って)作成されていると、仮定する。
@javax.ejb.Singleton
public class ChatRoom {
private java.util.Map<User,Collection<String>> userComments;
@PostConstruct
public void setup (){
userComments = new java.util.concurrent.ConcurrentHashMap<User,Collection<String>>();
/* ...*/
}
public void join(User usr){ /* ...*/ }
public void disconnect(User usr){ /* ...*/ }
public void say(String msg){ /* ...*/ }
}
全てのクライアントは、単に、ChatRoom
インスタンスへの参照を取得して、同じミュータブルな状態を更新できる。クライアントは、取得の際に、同じインスタンスへのアクセスを保証されている。これは、セッションbeanなので、他のいかなるセッションbeanと同じ保障を提供している:このbeanは、完全にスレッドセーフである。
@javax.ejb.EJB
private ChatRoom chatRoom ;
/* ... */
chatRoom.join(myUser) ;
chatRoom.say( "Hello, world!");
chatRoom.disconnect();
シングルトンは、コンカレントなアクセスができるように設計されている。今回の仕様で、開発者は、並列処理に関して精巧な制御ができるようになった。この動きは、デフォルトである。明示的に、コンテナ管理のコンカレンシィ(並行処理)を使うことが宣言できる、そのためには、@javax.ejb.ConcurrencyManagement(CONTAINER)
とアノテートする。もしbeanをもっとコントロールしたければ、@javax.ejb.ConcurrencyManagement(BEAN)
を使う。コンテナ管理のコンカレンシィは、メソッドレベルあるいはクラスレベルで、アクセスのタイプを明記することが必要である。デフォルトで、以下のように規定してもよい、すべてのビジネスメソッドは、クラスレベルで@javax.ejb.Lock(WRITE)
を使って、シリアライズされ、それから、状態の変更による2次的な問題がないように、メソッドは、事実上、"read-only"になるように場合には、最適化する。read-onlyのメソッドは、@Lock(READ)
でアノテートする。@Lock(WRITE)
でアノテートされたメソッドへの全てのアクセスは、シリアライズされ、終了するまで、あるいは、タイムアウトが起きるまで、クライアントからのアクセスは、ブロックされる。java.util.concurrent.TimeUnit
の値をとる@AccessTimeout
アノテーションを使って、タイムアウトの長さを制御することができる。このコンカレンシィ制御を利用するために、ChatRoom
の最初の実装コードを変更することができる。
@javax.ejb.Singleton
@
javax.ejb.
Lock(WRITE)
public class ChatRoom {
private java.util.Map<User,Collection<String>> userComments;
@PostConstruct
public void setup (){
userComments = new java.util.concurrent.ConcurrentHashMap<User,Collection<String>>();
/* ...*/
}
public void join(User usr){ /* ...*/ }
public void disconnect(User usr){ /* ...*/ }
public void say(String msg){ /* ...*/ }
@javax.ejb.Lock(READ)
public int getCountOfActiveUsers(){ /* ... run through the map and count ... */ }
}
当然、ChatRoom
のようなものは、ユーザとポストの数が、アプリケーションサーバのメモリを使い尽くせば、最終的にメモリ枯渇で死んでしまう。デモの目的で、ポストされているChatRoom
を検索して、LRUが古いチャットデータを破壊する(あるいは、おそらくJPA と EntityManager
を使って保持し、それから破壊する)定期的なガーベッジコレクタを想像しよう。
EJB Timer
EJB 2.1以来、EJBは、タイマのメカニズムを持っている。しかし、ミリ秒間隔でいつも動くものなので、設定がかなり面倒である。EJB 3.0では、その状況が少し改善されたが、タイマが、本質的に手続き的に宣言され、間隔ベースなことは、変わらないままだった。例えば、もし週の頭に何かをセットアップしたいと思うなら、大変なことだった。EJB 3.1は、Quartz や Fluxのような様々な他のスケジューラをやめて、宣言型で、柔軟性のあるタイマサービスを提供することによって、事態を改善した。CRONのようなスケジューリングを含んで、最も簡単なスケジューリングのニーズには、EJB 3.1は、よいソリューションである。我々のChatRoom
に戻ることにする。古いデータを調べて、ガーベッジコレクトするbeanをスケジュールしたいと思う。
@javax.ejb.Singleton
public class ChatRoom {
private java.util.Map<User,Collection<String>> userComments;
@javax.ejb.Schedule(minute="1", hour="*")
public void cleanOutOldDataFromChatLogs() {
/** ... not reprinting all the existing code ... */
}
}
書いたメソッドは、データを検索し、高速なチェックを行う-必要なら-古い/関係のないチャットログを消去するので、管理可能な状況に保つことができる。ここでは、メソッドが1時間毎に必ず走るような、宣言型のモデルを使った。もちろん、EJB 3.0のTimerServiceを使うこともできる。
Interfaceなしのビュ-
EJB 3.0では、beanは、最低1つのinterface(ローカルあるいは、リモートのビジネスinterface)をサポートする必要があった。Interfaceが、beanのクライアントにとって、beanのビューとして使うことができるからである。interface経由の間接参照は、非常に強力な技法であるが、時々事を複雑にするだけのことがある。EJB 3.1では、interfaceを持たないbeanを書くことができる。その場合、クライアントへのビューは、クラスが見せる公開メソッドである。
@javax.ejb.Stateless
public class Calculator {
public float add ( float a , float b) {
return a + b;
}
public float subtract (float a , float b){
return a - b ;
}
}
このbeanのクライアントは、普通に、単に注入によりbeanを取得し、起動する。:
@javax.ejb.EJB
private Calculator calculator;
...
float result = calculator.add( 10 , 10 ) ;
...
非同期サービス
スケーリング問題を扱う最も単純な方法は、(能力が伴なうまで)この問題を扱わないことである!このアプローチは、最も有名には、SEDA(段階的イベントドリブンアーキテクチャ)パターンの特徴があることであり、ボトルネックは、キューイングすることで避ける。このため、依頼した仕事は、キューに入れられ、クライントは、先に進むことができる。もしコンポーネントのダウンストリームに時間がかかり、システムに負荷がかかっている場合、このパターンが、遅いコンポーネントによって、システムがダウンしないことを保証する。
スケーリングに対するもう一つのアプローチは、一方向のメッセージ交換で、クライアントからの呼び出しをブロックしないことである。別のアプローチは、非同期で単純に動き、結果が戻るまで、クライント側での実行を進める。これら全てのアプローチは、EJB 3.1における、サービスの新しい非同期サポートにより実現した。beanクラスや個々のメソッドを@javax.ejb.Asynchronous
でアノテートすると、呼び出しの結果が戻るまで、クライアントは、ブロックされるべきでないことをコンテナに言うことになる。これにより、クライアントは、直ちに、続行し、そして-理論的には-コンテナが、作業をバッファして、都合のいい時に実行できる、ようにできる。
もしbeanクラスかビジネスinterfaceが@Asynchronous
でアノテートされていれば、beanのすべてのメソッドは、保留される。さもないと、@Asynchronous
でアノテートされたメソッドだけが保留される。非同期メソッドは、voidか java.util.concurrent.Future<V>.
のインスタンスを返す。クライアントは、後で任意の時に、結果の Future<V>
のインスタンスを調べることができるが、呼出しの結果は、直ちに、クライアント側のスレッドで存続し、ブロックしない。このように、EJBが1時間2時間かかろうが、問題ではなく、クライアントは、影響されない。概念的には、これは、JMSキューにリクエストを送る、という単独のジョブでサービスを呼ぶのと同じである。
Future<V>
のインスタンスは、ジョブのキャンセルや結果を待つのに使わうこともできる。クライアントにおける処理中のコンテキストは、非同期メソッドには、伝搬しない。その時、 REQUIRED
は、実際上、非同期メソッドに対しては、REQUIRES_NEW
である。
例をみよう:いくつものwebサービスと通信し、その結果を集めるサービスを作ろうと思う。結果は、欲しいが、クライアントのリクエスト(webページ、たぶん?)を止めたままにできない。そのようなサービスは、次のようになるだろう:
@javax.ejb.Stateless
public CarHotelAndAirLineBookingServiceBean implements CarHotelAndAirLineBookingService {
@javax.ejb.Asynchronous
public Future<BookingConfirmation> bookCarHotelAndAirLine( Car rental, Hotel hotel, AirLine airLine) {
/** ... */
}
}
クライアント側-JSFのアクション-そのようなサービスを次のように呼び出す:
@Named
public BookingAction {
@javax.ejb.EJB private CarHotelAndAirLineBookingServiceBean bookingService;
private Future<BookingConfirmation> confirmation;
public String makeRequest(){
Hotel hotelSelection = ... ;
Car rentalSelection = ... ;
AirLine airLineSelection = ... ;
confirmation = bookingService.bookCarHotelAndAirLine(
rentalSelection, hotelSelection, airLineSelection ) ;
return "showConfirmationPending";
}
public Future<BookingConfirmation> getConfirmation(){ /* ... */ }
/* ... */
}
簡単になったデプロイ
EJB 3.1は、また.WAR
ファイル内部へのデプロイをサポートする、という劇的に簡単なデプロイのパラダイムを提供する、最初のバージョンの仕様となった。コンポーネント定義のアノテーションを伴なうクラスは、WEB-INF/classes
ディレクトリ内に、あるいは、WEB-INF/lib.
ディレクトリ内の.jarとしてパッケージされた時に、enterprise beanコンポーネントになる。enterprise beanは、またWEB-INF/ejb-jar.xml
ファイルを使っても、定義できる。.WAR
にパッケージされたbeanは、1つのネームスペースを共有し、そして.WAR
の環境の一部になる。WEB-INF/lib
ディレクトリ内の.jarにパッケージ化するのは、こうして、意味的には、WEB-INF/classes
ディレクトリ内にクラスを置くのと等価である。
新しい仕様の別の新奇のフィーチャが、EJB Liteである。たくさんのアプリケーションにとって、EJBテクノロジは、必要以上のものである。EJB Liteは、session-beanコンポーネントの使用を中心とした、もっと効率化したサブセットを提供する。これは、EJBコンポーネントを組み込み可能なように使え、ユニットテストも単純にする方法を提供する。EJB Liteは、ステートレス、ステートフルそしてシングルトン セッションbeanをサポートする。Beanは、ローカルなinterfaceあるいは、interface無しのビューを持つことができる。これらは、インターセプターと協働でき、トランザクションやセキュリティのようにコンテナのサービスを使うことができる。
EJB 3.1は、開発者用のツールキットの中の強力なツールであり、明らかに、たいていのアプリケーションの80%のユースケースに合うものに、進化してきた。将来は、この仕様にとって良い方向である。この仕様は、Java SEの削除メカニズムを使って、あるフィーチャを将来の削除の標的にしている、最初のリリースでもある。将来、取り除かれる可能性のある仕様としては、以下のものがある。コンテナ管理による永続性とbean管理による永続性の古い型への3.0以前サポート、エンティティbeanのEJB 2.1仕様のクライアントビュー、EJB QL(EJB 2.1からのクエリ言語)そして、JAX-RPCベースのwebサービスサポート(両方のエンドポイントとクライアントビュー;これらは、J2EE 1.4で加えられた)。
明らかに、EJB 3.1は、実にすばらしい、後方互換なアップグレードであり、5年以上も前のJSR 220 (EJB 3.0)で始まった開発からのすばらしい次のステップを示した。