月曜日 8 07, 2006
月曜日 8 07, 2006
Java EE 5でDependency Injection(DI)を利用する上で、もう一つ気をつけなければいけない点は、DIされるオブジェクトのコンカレント・アクセスと参照するオブジェクトがthread-safeかどうかです。
DIを利用する上での鉄則は、「コンカレント・アクセスされるオブジェクトにthread-safeでないオブジェクトをインジェクションしてはいけない」ということです。例えば、 ServletオブジェクトはWebコンテナ内で一つだけ生成され、複数のスレッドから同時にアクセスされます。EntityManagerはthread-safeでないオブジェクトですから、以下のように、ServletにEntityManagerをインジェクションしてはいけないことになります([1])。
public class BookShoppingServlet extends HttpServlet {
@PersistenceContext EntityManager em; // NG: thread-safeでない
protected void doPost(HttpServletRequest req,
HttpServletResponse res) throws ... {
Order order = ...;
em.persist(order);
}
}
ここで、DI可能なオブジェクトのライフサイクル/コンカレンシーと、参照可能なオブジェクトのthread-safetyについてまとめておきましょう。まず、前者に関する一覧です。
| java type | multicity | concurrent access |
|---|---|---|
| サーブレット |
web.xmlの<servlet>要素毎に1つ | Y |
| サーブレット・フィルタ | web.xmlの<filter>要素毎に1つ | Y |
| リスナ | web.xmlの<listener>要素毎に1つ | Y |
| タグハンドラ | HTTPリクエスト毎、JSP内のタグ定義ごと | N |
| JSFマネージド・ビーン | applicationスコープ:Webモジュール内<managed-bean>要素毎に1つ | Y |
| sessionスコープ:セッション毎に1つ | maybe | |
| requestスコープ:HTTPリクエスト毎、<managed-bean>要素毎に1つ | N | |
| セッション・ビーン, メッセージ・ドリブン・ビーン, インターセプター |
コンテナの設定による。通常複数インスタンスがプールされる | N |
| Webサービス・エンドポイント |
コンテナの実装による | Y(servlet)/N(ejb) |
| ハンドラ | コンテナの実装による | ? |
| メイン・クラス | クライアント・コンテナ毎に1つ | maybe |
オブジェクトの種類毎にそのライフサイクルは異なりますが、インジェクションが行なわれるのは基本的に対象のオブジェクトがインスタンス化された直後の1回だけであることに注意して下さい。
上記の表に示したように、サーブレット、サーブレット・フィルタ、リスナは全て、コンカレントアクセスされるオブジェクトであるため、インジェクションするオブジェクトのthread-safteyに注意が必要です。JSFマネージド・ビーンは、コンフィギュレーションに設定するスコープによって、コンカレンシーの振舞が異なります。リクエスト・スコープに設定した場合、そのビーンはリクエスト毎に生成されますので、single thread model(1つのスレッドだけが1つのオブジェクトにアクセスされること)が保証されます。JSFマネージド・ビーンをセッション・スコープに設定した場合は、シンプルなアプリケーションでは複数スレッドでアクセスされることはありませんが、Webページが複数ウィンドウ/フレームとなる場合やAJAXを使用する場合には、複数スレッドでアクセスされる可能性があることに注意する必要があります。
EJBの場合は、コンテナによってリクエスト毎にsingle thread modelとなることが保証されています。インターセプターのライフサイクルはwiringされたEJBと同じライフサイクルになることから、やはりコンカレントアクセスはされません。
Webサービスの場合は、servlet形式でデプロイされる場合とEJB形式でデプロイされる場合の2通りがあります。前者の場合はservletと同様複数スレッドアクセスされることになります。Webサービスハンドラは、JAX-WS仕様の記述からだけでは判断することができないため、実装依存ということになります。
次に、インジェクションで参照されるオブジェクトがthread-safeかどうかについて、以下の表にまとめました。
| Java type | thread-safe |
|---|---|
| 基本データ型(String, Character, Integer, Boolean, Double, Byte, Short, Long, Float) | Y (if read-only) |
| javax.sql.DataSource |
? |
| javax.jms.ConnectionFactory, javax.jms.QueueConnectionFactory, javax.jms.TopicConnectionFactory |
Y *1) |
| javax.mail.Session | ? |
| java.net.URL | Y (if read-only) |
| javax.resource.cci.ConnectionFactory | ? |
| javax.resource.cci.InteractionSpec | N *2) |
| javax.jms.Queue, javax.jms.Topic |
Y *1) |
| javax.transaction.UserTransaction | Y *3) |
| javax.transaction.TransactionSynchronizationRegistry | Y *4) |
| org.omg.CORBA.ORB | maybe *5) |
| javax.persistence.EntityManagerFactory | Y *6) |
| javax.persistence.EntityManager | N *7) |
| EJB参照 | ? |
| Webサービス/ポート参照 | ? |
| javax.ejb.TimerService | Y (in ejb) *8) |
| javax.ejb.EJBContext | Y (in ejb) *8) |
基本的に、JSRのスペックにthread-safeの明示がある場合にのみ、Y/Nを明記しています。上記の表で、?マークのものはベンダ毎に異なると考えて下さい。ただし、判断が微妙なものもあります。例えば、DataSourceについては、J2EEパターンのサービス・ロケータ・パターンでリソース参照のキャッシュを推奨していたことから、歴史的にはほとんどのベンダのAppServerではDataSourceはthread-safeとして実装されています([2])。しかしながら、ベンダに依存しないポータビリティを重要視するのであれば、表中の?マークのものはthread-safeでないものとして考えた方がいいでしょう。
また、現在公開されているJTA 1.0のスペックでは、UserTransactionがthread-safeであるとは明示的には言及されていません。この件に関してJSR-907のスペック・リードであるSankara Raoに確認したところ、現実的にはUserTransactionはthread-safeと考えてよいということです。Java EE 5が参照しているJTA 1.1は現在メンテナンス・レビュー中ですが、この点についてはJTA 1.1のスペックに明示されるようになるそうです。したがって、以下のように、ServletにUserTransactionをインジェクションするのは問題ないということができます。
public class BookShoppingServlet extends HttpServlet {
@Resource UserTransaction utx; // OK: thread-safe
@PersistenceUnit EntityManagerFactory emf; // OK: thread-safe
protected void doPost(HttpServletRequest req,
HttpServletResponse res) throws ... {
utx.begin();
EntityManager em = emf.createEntityManager();
Order order = ...;
em.persist(order);
utx.commit();
em.close();
}
}
インジェクションするオブジェクトのthread-safteyに関してはいくつかの種類のオブジェクトでは微妙な状態です。しかしながら、EJBをインジェクション対象のオブジェクトにした場合、thread-safeの問題は全く心配する必要がなくなります。なぜなら、EJBはコンテナによってsingle thread modelとなることが保証されているからです。DIはJava EE 5のスペック全体に渡って使用可能になっていますが、DIを最も気楽に使用できるのはEJBであると言えます。
※なお、このエントリに示した情報は慎重に調査した結果を紹介しておりますが、もし内容に誤りがありましたら、是非ご指摘頂ければと思います。
[1] Java Persistence APIをWebコンテナで使用する際の注意については、以下の情報が参考になります。特に、2つ目のJavaOneのスライドはトランザクション、ステート管理(conversational state)という観点からJava EE 5を使用する上で大変重要なポイントを説明しています。
[2] DataSourceとEJBホーム・インタフェースがthread-safeかどうかのについて他にも以下のような有益な議論があります。