Seam における 2 つの中心的概念は、 コンテキスト の概念とコンポーネントの概念です。 コンポーネントは、ステートフルなオブジェクト、通常は EJB です。 コンポーネントのインスタンスは、コンテキストと関連づけられ、そのコンテキスト中で名前を与えられます。 バイジェクション (Bijection) は、内部のコンポーネント名 (インスタンス変数) をコンテキスト中の名前にエイリアスし、 Seam によるコンポーネントツリーの動的な組み立て、再組み立てを可能にするメカニズムを提供します。
Seam に組み込まれたコンテキストから説明を始めましょう。
Seam コンテキストはフレームワークによって生成、破棄されます。 アプリケーションは Java API 呼び出しによってコンテキスト区分 (demarcation) を明示的に制御することはできません。 コンテキストは通常、暗黙的ですが、場合によってコンテキストはアノテーションによって区分されます。
基本の Seam コンテキストは以下の通りです。
ステートレスコンテキスト (Stateless context)
イベント (またはリクエスト) コンテキスト (Event (or request) context)
ページコンテキスト (Page context)
対話コンテキスト (Conversation context)
セッションコンテキスト (Session context)
ビジネスプロセスコンテキスト (Business process context)
アプリケーションコンテキスト (Application context)
これらのコンテキストのいくつかは、サーブレットや関連する仕様書から由来していることがわかります。 しかし、このうち 2 つは目新しいかもしれません。 対話コンテキストとビジネスプロセスコンテキストです。 WEB アプリケーション中での状態管理がとても脆弱でエラーが発生しやすい 1 つの理由は、 3 つの組み込みコンテキスト (リクエスト、セッション、アプリケーション) がビジネスロジックの観点から特定の意味を持たないからです。 例えば、実際のアプリケーションのワークフローの観点から見るとユーザログインセッションは極めて自由裁量な構造です。 そのため、ほとんどの Seam コンポーネントは、対話コンテキストあるいはビジネスプロセスコンテキストのスコープに配置されます。 なぜなら、それらはアプリケーションの観点からとても意味のあるコンテキストだからです。
順に、各コンテキストを見てみましょう。
本当に状態をもたないコンポーネント (主にステートレスセッション Bean) は、いつもステートレスコンテキスト (実際にはコンテキストではありません) に置かれます。 ステートレスコンポーネントは、あまり興味深いものでもなく、まったくオブジェクト指向でもありません。 でも、これらは重要でしばしば役に立ちます。
イベントコンテキストは「最も狭い」状態を持つコンテキストで、 他の種類のイベントを網羅する WEBリクエストコンテキストの概念の一般化です。 それにもかかわらず、JSF リクエストのライフサイクルと関連づけられたイベントコンテキストは、 イベントコンテキストの最も重要な用例であり、最もよく利用されるものです。 リクエスト終了時に、イベントコンテキストに関連するコンポーネントは破棄されますが、 それらの状態は、少なくともリクエストのライフサイクルの間では有効かつ明確です。
RMI 経由あるいは Seam Remoting により Seam コンポーネントを呼び出すとき、 イベントコンテキストは、その呼び出しだけのために生成、破棄されます。
ページコンテキストは、状態をレンダリングされたページの特定のインスタンスと関連づけを可能にします。 イベントリスナ中で状態の初期化が可能で、 実際にページをレンダリングしている間に、 ページに由来するどんなイベントからも状態にアクセスが可能です。 これは特にサーバサイドのデータ変化にリストが連動しているクリッカブルリストのような機能に役立ちます。 状態は実際にクライアントのためにシリアライズされます。 そのため、この構造は複数ウインドの操作や戻るボタンに対して極めて堅牢です。
対話コンテキストは Seam でまさに中心となるコンセプトです。 対話 (conversation) は、ユーザの観点からの作業単位です。 それはユーザとのインタラクション、リクエスト、およびデータベーストランザクションをまたぐかもしれません。 しかし、ユーザにとって対話は、1 つの問題を解決します。 例えば、「ホテル予約」、「契約承認」、「注文作成」はすべて対話です。 対話というものが 1 つの「ユースケース」あるいは「ユーザストーリ」を実装していると考えたいかもしれませんが 関係は必ずしもその通りにはなりません。
対話は、「ユーザがこのウィンドウの中で現在していること」と関連づけられた状態を保持します。 1 人のユーザは、通常マルチウィンドウで、ある時点に進行中の複数の対話を持っているかもしれません。 対話コンテキストは、異なる対話からの状態の衝突をなくし、バグの原因とならないことを保証します。
対話の観点からアプリケーションについて考えることに慣れるには時間がかかるかもしれません。 しかし、慣れてしまうと、このコンセプトが大好きになり、もう対話なしでは考えられなくなるだろうと思います。
ある対話は単に 1 つのリクエストの間続いています。 複数のリクエストをまたぐ対話は、Seam によって提供されたアノテーションを使って、区分を示されなければなりません。
ある対話はまた タスク です。 タスクは長期ビジネスプロセスの観点では重要な意味を持つ対話であり、 タスクが首尾よく完了する場合、 ビジネスプロセスの状態遷移を引き起こす可能性を持っています。 Seam はタスク区分用に特別なアノテーションのセットを提供します。
より広い対話の "内部" で対話を発生させるような ネスト も可能です。 これは拡張機能です。
通常、実際にはリクエストとリクエストの間 servlet セッション中で Seam により対話状態は保持されます。 Seam は設定可能な 対話タイムアウト を実装し、 自動的に不活性な対話を破棄し、 ユーザが対話を中断しても、ユーザログインセッションにより保持される状態は際限なく増加しないことが保証されています。
Seam は同じプロセス中の同じ長期対話コンテキスト中で発生する並列のリクエスト処理をシリアル化します。
あるいは、Seam はクライアントブラウザの中に対話の状態を保持するように設定される場合もあります。
セッションコンテキストはユーザログインに関する状態を保持します。 いくつかの事例では対話の間で状態を共有することが有用なことがありますが、 ログインユーザに関するグローバル情報以外のコンポーネントを保持するために、 セッションコンテキストを使用することは賛成できません。
JSR-168 portal 環境では、セッションコンテキストは portlet セッションを意味します。
ビジネスプロセスコンテキストは長期ビジネスプロセスに関する状態を保持します。 この状態は BPM エンジン (JBoss jBPM) によって管理や永続化が行われます。 ビジネスプロセスは、複数ユーザの複数インタラクションを橋渡しします。 従って、この状態は複数ユーザの間で明確な方法で共有されます。 現在のタスクは現在のビジネスプロセスインスタンスを決定し、 ビジネスプロセスのライフサイクルは プロセス定義言語 を使用することで外部に定義されます。 従って、ビジネスプロセスの区分のために特別なアノテーションはありません。
アプリケーションコンテキストはサーブレット仕様からおなじみのサーブレットのコンテキストです。 アプリケーションコンテキストは主に、設定データ、参照データ、メタモデルのような静的な情報を保持するために役立ちます。 例えば、Seam はアプリケーションコンテキスト中に Seam 設定やメタモデルを保管しています。
コンテキストは名前空間、コンテキスト変数 のセットを定義します。 これらはサーブレット仕様のセッションやリクエスト属性と同様に機能します。 どのような値でもコンテキスト変数とバインドができますが、 通常、Seam コンポーネントインスタンスをコンテキスト変数にバインドします。
従って、コンテキスト中では、 コンポーネントインスタンスは、コンテキスト変数名 (いつもではないが通常はコンポーネント名と同じ) で識別されます。 Contexts クラスを通して特定のスコープの指定されたコンポーネントインスタンスにプログラム的にアクセスもできます。 それは Context インタフェースのスレッドに結びついたインスタンスへのアクセスを提供します。
User user = (User) Contexts.getSessionContext().get("user");
名前に関連する値を設定したり変更したりすることもできます。
Contexts.getSessionContext().set("user", user);
しかしながら、通常、インジェクションを通してコンテキストからコンポーネントを取得し、 アウトジェクションを通してコンポーネントインスタンスをコンテキストに配置します。
上記のように、コンポーネントインスタンスは特定の周知のスコープから取得することもありますが、 それ以外の場合、すべてのステートフルスコープは 優先順位 に従って検索されます。 その順序は以下の通りです。
イベントコンテキスト
ページコンテキスト (Page context)
対話コンテキスト (Conversation context)
セッションコンテキスト (Session context)
ビジネスプロセスコンテキスト (Business process context)
アプリケーションコンテキスト (Application context)
Contexts.lookupInStatefulContexts() を呼び出すことによって優先順位の検索も可能です。 JSF ページから名前によってアクセスする場合はいつも、優先順位検索が発生します。
servlet 仕様も EJB 仕様も同じクライアントから起こる同時並行のリクエストを管理するための設備はまったく定義していません。 servlet コンテナは単純にすべてのスレッドを同時並行的に稼動させ、 スレッドセーフとすることをアプリケーションコードに任せます。 EJB コンテナはステートレスコンポーネントが同時並行的にアクセスされることを可能にし、 複数のスレッドがひとつのステートレスセッション Bean にアクセスするならば例外をスローします。
この振る舞いは粒度の細かい同期リクエストをベースとする古いスタイルの Web アプリケーションでは大丈夫であったかもしれません。 しかし、多くの粒度の細かい非同期リクエスト (AJAX) を多用する最新のアプリケーションのために、 同時並行はまぎれもない事実であり、プログラムモデルとしてサポートされなければなりません。 Seam は同時並行管理レイヤをコンテキストモデルに織り込みます。
Seam セッションとアプリケーションコンテキストはマルチスレッドになっています。 Seam は同時並行的に処理されるためにコンテキスト中での同時並行リクエストを許します。 イベントとページコンテキストは本来シングルスレッドです。 厳密に言えばビジネスプロセスコンテキストはマルチスレッドですが、 実際には同時並行はとてもまれで、この事実はほとんど着目されないかもしれません。 最後に、 同じ長期対話コンテキスト中の同時並行リクエストをシリアライズすることによって、 Seam は、対話コンテキストのために プロセスごと対話ごとのシングルスレッド モデルを実施します。
セッションコンテキストはマルチスレッドで、よく揮発性の状態を含むので、 Seam によりセッションスコープコンポーネントは同時並行アクセスからいつも保護されています。 Seam はデフォルトでリクエストを セッションスコープセッション Bean と JavaBean にシリアライズします。 ( そして、発生するどんなデッドロックも検出して打開します。) アプリケーションスコープのコンポーネントは通常揮発性の状態を保持しないため、 これはアプリケーションスコープのコンポーネントのためのデフォルトの振る舞いではありません。 なぜなら、グローバルレベルの同期化は 極端に コストがかかるからです。 しかし、 @Synchronized アノテーションを追加することで、 セッション Bean また JavaBean コンポーネントにシリアライズされたスレッドモデルを強制可能です。
この同時並行モデルは、 開発者側での特別な作業をまったく必要とすることなく、 AJAX クライアントが安全に揮発性セッションや対話状態を使用できることを意味します。
Seam コンポーネントは POJO (Plain Old Java Objects) です。 具体的には、Seam コンポーネントは JavaBean もしくは EJB 3.0 エンタープライズ Bean です。 Seam は コンポーネントが EJB が必須ではなく、また EJB 3.0 準拠のコンテナがなくても使用できますが、 Seam は EJB 3.0 を念頭にして設計され、EJB 3.0 と強く統合されています。 Seam は以下の コンポーネントタイプ をサポートします。
EJB 3.0 ステートレスセッション Bean
EJB 3.0 ステートフルセッション Bean
EJB 3.0 エンティティ Bean
JavaBean
EJB 3.0 メッセージ駆動型 Bean
ステートレスセッション Bean コンポーネントは、複数の呼出しに対して状態を保持することができません。 従って、それらは通常さまざまな Seam コンテキスト内の別コンポーネントの状態を操作するのに役に立ちます。 それらは JSF のアクションリスナとして使用できるかもしれませんが、 表示のために JSF コンポーネントにプロパティを提供することはできません。
ステートレスセッション Bean はいつもステートレスコンテキストに置かれます。
ステートレスセッション Bean は最も興味のわかない種類の Seam コンポーネントです。
ステートフルセッション Bean コンポーネントは、 Bean の複数の呼出しに対して状態を保持することができるだけでなく、 複数のリクエストに対して状態を保持することもできます。 データベースに保持されていないアプリケーションの状態は、 通常、ステートフルセッション Bean によって保持される必要があります。 これは Seam と他の多くの WEB アプリケーションフレームワークとの大きな違いです。 現在の対話の情報を直接 HttpSession に押し込める代わりに、 対話コンテキストに結びついたステートフルセッション Bean のインスタンス変数の中にそれを保持すべきです。 これは、Seam がこの状態のライフサイクルの管理を可能にし、 異なる同時実行中の対話に関連する状態の間に衝突がないことを保証します。
ステートフルセッション Bean はしばしば JSF アクションリスナ、または、 表示もしくはフォームのサブミットのためにプロパティを提供する JSF コンポーネントのバッキング Bean として使用されます。
デフォルトで、ステートフルセッション Bean は対話コンテキストとバインドします。 それらはページもしくはステートレスコンテキストとバインドできません。
セッションスコープのステートレスセッション Bean への同時並行リクエストは、 いつでも Seam によってシリアライズされます。
エンティティ Bean はコンテキスト変数とバインドし、Seamコンポーネントとして機能することもあります。 エンティティは、コンテキスト依存識別子に加えて永続識別子を持つために、 エンティティのインスタンスは、Seam によって暗黙的にインスタンス化されるより、 むしろ Java コード中で明示的にバインドされます。
エンティティ Bean コンポーネントはバイジェクションもコンテキスト区分もサポートしません。 また、エンティティ Bean トリガのデータ妥当性検証の呼び出しもサポートしていません。
エンティティ Bean は、通常 JSF アクションリスナとして使用されませんが、 しばしば、表示あるいはフォームのサブミットのために JSF コンポーネントにプロパティを提供するバッキング Bean として機能します。 特に、エンティティ Bean をバッキング Bean として使用することは一般的であり、 追加 / 変更 / 削除タイプの機能の実装のためのステートレスセッション Bean アクションリスナといっしょに使用されます。
デフォルトで、エンティティ Bean は対話コンテキストとバインドします。 ステートレスセッション Bean とはバインドしません。
クラスタリングされた環境では、 ステートフルセッション Bean 中でエンティティ Bean の参照を保持することより、 エンティティ Bean を直接的に対話あるいはセッションスコープの Seam コンテキスト変数にバインドする方が非効果的であることに留意してください。 この理由のため、すべての Seam アプリケーションが Seam コンポーネントであるためにエンティティ Bean を定義するわけではありません。
JavaBean はステートレスあるいはステートフルセッション Bean のように使用されることもあります。 しかし、それらはセッション Bean の機能を提供していません。 (宣言的トランザクション区分、 宣言的セキュリティ、 効率的にクラスタ化された状態レプリケーション、 EJB 3.0 永続性、 タイムアウトメソッドなど)
後の章で、EJB コンテナなしで Seam や Hibernate を使用する方法を紹介しています。 このユースケースでは、コンポーネントはセッション Bean の代わりに JavaBean です。 しかし、多くのアプリケーションサーバでは、 ステートフルセッション Bean コンポーネントをクラスタリングするより、 対話あるいはセッションスコープの Seam JavaBean コンポーネントをクラスタリングする方が多少非効率的であることに留意してください。
デフォルトで、JavaBean はイベントコンテキストとバインドします。
セッションスコープの JavaBean への同時並行リクエストはいつも Seam によりシリアライズされます。
メッセージ駆動形 Bean は Seam コンポーネントとして機能することができます。 しかし、メッセージ駆動型 Bean は、他の Seam コンポーネントとまったく異なった形で呼び出されます。 コンテキスト変数を通じてそれらを呼び出す代わりに、 JMS queue あるいは、topic に送信されたメッセージを待ち受けます。
メッセージ駆動形 Bean は、Seam コンテキストとバインドできません。 また、それらの「呼び出し元」のセッションや対話状態にアクセスできません。 しかし、メッセージ駆動形 Bean は、バイジェクションと他の Seam の機能をサポートします。
そのマジック (バイジェクション、コンテキスト区分、データ妥当性検証など) を実行するために、 Seam はコンポーネントの呼び出しをインターセプトしなければなりません。 JavaBean では、Seam はコンポーネントのインスタンス化を完全に制御するため、特別な設定は不要です。 エンティティ Bean では、バイジェクションとコンテキスト区分が指定されていないため、インターセプションは不要です。 セッション Bean では、EJB インターセプタをセッション Bean コンポーネントのために登録しなければなりません。 アノテーションは以下のように使用します。
@Stateless @Interceptors(SeamInterceptor.class) public class LoginAction implements Login { ... }
しかし、もっと良い方法は、ejb-jar.xml にインターセプタを定義することです。
<interceptors> <interceptor> <interceptor-class>org.jboss.seam.ejb.SeamInterceptor</interceptor-class> </interceptor> </interceptors> <assembly-descriptor> <interceptor-binding> <ejb-name>*</ejb-name> <interceptor-class>org.jboss.seam.ejb.SeamInterceptor</interceptor-class> </interceptor-binding> </assembly-descriptor>
すべての Seam コンポーネントは名前が必要です。 @Name アノテーションを使用してコンポーネントに名前を割り当てます。
@Name("loginAction") @Stateless public class LoginAction implements Login { ... }
この名前は、Seam コンポーネント名 (seam component name) で、 EJB 標準で定義された他の名前との関連はありません。 しかし、Seam コンポーネント名はちょうど JSF 管理 Bean のように動作するため、 2 つのコンセプトは同一と考えることができます。
@Name はコンポーネント名を定義する唯一の方法ではありませんが、 いつも、どこかで 名前を指定する必要があります。 もしそうしないと、他の Seam アノテーションはどれも機能しないでしょう。
ちょうど JSF のように、Seam コンポーネントインスタンスは、 通常コンポーネント名と同じ名前のコンテキスト変数と結合します。 従って、例えば、Contexts.getStatelessContext().get("loginAction") を使って、 LoginAction にアクセスできるでしょう。 具体的には、Seam 自身がコンポーネントをインスタンス化する時はいつでも、 それはコンポーネント名によって新しいインスタンスを変数と結合します。 しかし、この場合も JSF のように、 アプリケーションはプログラムに基づいた API コールによってコンポーネントを他のコンテキスト変数と結合させることも可能です。 特定のコンポーネントがシステムの中で複数のロールを提供する場合のみ、これは有用です。 例えば、現在のログイン User は currentUser セッションコンテキスト変数に結合されているかもしれませんが、 一方で、ある管理機能の主体である User は user 対話コンテキスト変数に結合されているかもしれません。
非常に大規模なアプリケーションのために、そして組み込み Seam コンポーネントのために、修飾名はしばしば使われます。
@Name("com.jboss.myapp.loginAction") @Stateless @Interceptors(SeamInterceptor.class) public class LoginAction implements Login { ... }
Java コード中でも JSF の式言語中でも修飾されたコンポーネント名は使用できます。
<h:commandButton type="submit" value="Login" action="#{com.jboss.myapp.loginAction.login}"/>
これはうっとうしいので、Seam は修飾名を簡単な名前にエイリアスする機能も提供します。 以下のような行を components.xml ファイルに追加してください。
<factory name="loginAction" scope="STATELESS" value="#{com.jboss.myapp.loginAction}"/>
すべての組み込み Seam コンポーネントは修飾名を持っていますが、 それらのほとんどは Seam jar に含められている components.xml ファイルによって簡単な名前にエイリアスされます。
@Scopeアノテーションを使用して、コンポーネントのデフォルトスコープ (コンテキスト) をオーバーライドすることができます。 これによって Seam によってインスタンス化される時に、 コンポーネントインスタンスがどんなコンテキストと結合するかを定義ができます。
@Name("user") @Entity @Scope(SESSION) public class User { ... }
org.jboss.seam.ScopeType 可能なスコープの列挙を定義します。
一部の Seam コンポーネントクラスはシステムの中で複数のロールを果たすことができます。 例えば、セッションスコープのコンポーネント User クラスは、 よく現在のユーザとして使用されますが、ユーザ管理画面では対話スコープのコンポーネントとして使用されます。 @Role アノテーションは、コンポーネントに対して異なったスコープを持つ追加指定のロールの定義を可能にしています。 これにより、 同じコンポーネントクラスを異なるコンテキスト変数にバインドすることができるようになります。 (どの Seam コンポーネント インスタンス も複数のコンテキスト変数にバインドが可能ですが、 これはクラスレベルで可能であり自動インスタンス化を利用しています。)
@Name("user") @Entity @Scope(CONVERSATION) @Role(name="currentUser", scope=SESSION) public class User { ... }
@Roles アノテーションは、欲しいだけ多くの追加のロールの指定を可能にします。
@Name("user") @Entity @Scope(CONVERSATION) @Roles({@Role(name="currentUser", scope=SESSION) @Role(name="tempUser", scope=EVENT)}) public class User { ... }
多くのよいフレームワークのように、 Seam は自分自身が提供している機能を使うことを心掛けている (Eat Your Own Dog Food)。 組み込みの Seam インターセプタ (後述) と Seamコンポーネントのセットで実装されています。 これは、アプリケーションがランタイムで組み込みのコンポーネントとやり取り行うことを容易にします。 更に、組み込みのコンポーネントを独自の実装に置き換えることによって Seam の基本機能をカスタマイズすることさえ容易にします。 組み込みのコンポーネントは Seam の名前空間 org.jboss.seam.core と 同じ名前の Java パッケージにおいて定義されます。
組み込みコンポーネントは、Seam コンポーネントと同様にインジェクトすることも可能ですが、 便利なスタティック instance() メソッドも提供します。
FacesMessages.instance().add("Welcome back, #{user.name}!");
Seam は、Java EE 5 環境において密に統合されるよう設計されています。 しかし、私たちは、完全な EE 環境で実行されない多くのプロジェクトがあることを理解しています。 TestNG や JUnit のようなフレームワークを使用した容易なユニットテストや統合テストが、 決定的に重要であることを理解しています。 そこで、組み込み Seam コンポーネントのインストールにより、 通常 EE 環境のみでしか見られない重要なインフラストラクチャのブートストラップを可能とし、 Java SE 環境で Seam を簡単に実行できるようにしました。
例えば、組み込みコンポーネント org.jboss.seam.core.ejb をインストールすることによって Tomcat あるいはテストスイート中で EJB3 コンポーネントを稼動させることが可能です。 それは JBoss 組み込み EJB3 コンテナ (JBoss Embeddable EJB3 container) を自動的にブートストラップし、EJBコンポーネントをデプロイします。
あるいは、もしあなたが EJB 3.0 のすばらしい新世界の用意ができていないならば、 組み込みコンポーネント org.jboss.seam.core.hibernate をインストールすることによって、 永続性のための Hibernate3 とともに JavaBean コンポーネントだけを使う Seam アプリケーションを書くことが可能です。 J2EE 環境の外で Hibernate を使う時には、 たぶん、JTA トランザクション管理と JNDI サーバが必要でしょう。 それは組み込みコンポーネント org.jboss.seam.core.microcontainer を通して使用可能です。 これはにより、 Tomcat のような SE 環境中で JBoss アプリケーションサーバ からの堅牢な (bulletproof) JTA/JCA プーリングデータソースを利用することが可能になります。
依存性の注入 あるいは 制御の逆転 は今ではもう大多数の Java 開発者に親しい概念です。 依存性の注入はあるコンポーネントが他のコンポーネントの参照を可能にします。 それはコンテナに setter メソッドあるいはインスタンス変数に他のコンポーネントを「インジェクト (注入) 」させることで実現します。 これまで見てきたすべての依存性の注入の実装では、 インジェクションはコンポーネントが生成されたときに起こり、 その後、参照はコンポーネントのライフサイクルの間で変化しません。 ステートレスコンポーネントにおいて、これは理にかなっています。 クライアントの観点から、特定のステートレスなコンポーネントのすべてのインスタンスは交換可能です。 一方、Seamはステートフルなコンポーネントの使用に重点を置いています。 従って、典型的な依存性の注入はもはやあまり有用な構造ではありません。 Seam はインジェクションの一般化として、バイジェクション (bijection) の概念を導入しました。 インジェクションと対照すると、バイジェクションは以下の通りです。
コンテキスト依存 - バイジェクションはさまざまな異なるコンテキストからステートフルなコンポーネントを組み立てるために使用されます。 (「より広い (wider) 」コンテキストからのコンポーネントは「より狭い (narrow) 」コンテキストからの参照も持つかもしれません。)
値はコンテキスト変数から呼ばれるコンポーネントの属性にインジェクトされ、 また、コンポーネント属性からコンテキストにアウトジェクト (outjected) され戻されます。 インスタンス変数そのものを設定することで、呼ばれたコンポーネントが簡単にコンテキスト変数の値を操作することを可能にします。
動的 - バイジェクションはコンポーネントが呼ばれるたびに発生します。 なぜなら、コンテキストの値は時間経過で変化し、 Seam コンポーネントがステートフルだからです。
本質的に、インスタンス変数の値をインジェクト、アウトジェクト、両方により指定することで、 バイジェクションはコンテキスト変数をコンポーネントのインスタンス変数にエイリアスを可能にします もちろん、バイジェクションを可能にするためにアノテーションが使用されています。
@In アノテーションは値がインスタンス変数にインジェクトされることを指定しています。
@Name("loginAction") @Stateless @Interceptors(SeamInterceptor.class) public class LoginAction implements Login { @In User user; ... }
あるいは、setter メソッド
@Name("loginAction") @Stateless @Interceptors(SeamInterceptor.class) public class LoginAction implements Login { User user; @In public void setUser(User user) { this.user=user; } ... }
デフォルトでは、 Seam はプロパティ名あるいはインジェクトされたインスタンス変数名を使用して、 すべてのコンテキストの優先順位検索を行います。 例えば、 @In("currentUser")を使用することで明示的にコンテキスト変数を指定することもできます。
指定されたコンテキスト変数と関連した既存のコンポーネントインスタンスが存在しないときに、 Seam にコンポーネントのインスタンスの生成を望むならば、 @In(create=true) を指定する必要があります。 値がオプションで (null でも可能) であれば、@In(required=false) を指定してください。
いくつかのコンポーネントでは、 それらが使用されるところではどこでも繰り返し @In(create=true) を指定する必要があるかもしれません。 このような場合、 コンポーネントに @AutoCreate アノテーションを付けることが可能で、 create=true を明示的に使用しなくても、 必要なとき常に作成されるようになります。
式の値をインジェクトすることも可能です。
@Name("loginAction") @Stateless @Interceptors(SeamInterceptor.class) public class LoginAction implements Login { @In("#{user.username}") String username; ... }
(コンポーネントライフサイクルとインジェクションについては次章により多くの情報があります。)
@Outアノテーションは、属性がインスタンス変数からもアウトジェクトされるべきことを指定します。
@Name("loginAction") @Stateless @Interceptors(SeamInterceptor.class) public class LoginAction implements Login { @Out User user; ... }
あるいは getter メソッドから
@Name("loginAction") @Stateless @Interceptors(SeamInterceptor.class) public class LoginAction implements Login { User user; @Out public User getUser() { return user; } ... }
属性値はインジェクトされることもアウトジェクトされることも可能です。
@Name("loginAction") @Stateless @Interceptors(SeamInterceptor.class) public class LoginAction implements Login { @In @Out User user; ... }
あるいは、
@Name("loginAction") @Stateless @Interceptors(SeamInterceptor.class) public class LoginAction implements Login { User user; @In public void setUser(User user) { this.user=user; } @Out public User getUser() { return user; } ... }
セッション Bean と エンティティ Bean の Seam コンポーネントは、 すべての EJB3.0 ライフサイクルコールバックメソッドをサポートします (@PostConstruct、@PreDestroy、など)。 Seam は @PreDestroy を除くこれらすべてのコールバックメソッドを JavaBean コンポーネントに拡張しています。 しかし、Seam はそれ自身のコンポーネントライフサイクルメソッドも定義します。
@Create メソッドは、Seam がコンポーネントをインスタンス化するときはいつでも、 呼び出されます。 @PostConstruct メソッドと違って、 このメソッドはコンポーネントが EJB コンテナによって作成された後に呼び出されます。 そして、一般的な Seam 機能のすべてにアクセスできます (バイインジェクションなど)。 コンポーネントは 1 つの @Create メソッドしか定義できません。
@Destroy メソッドは Seam コンポーネントがバインドしているコンテキストが終了するときに呼び出されます。 コンポーネントは唯一の @Destroy メソッドを定義できます。 ステートフルセッション Bean コンポーネントは、 @Destroy @Remove アノテーションを付けられたメソッドを定義することが 必須 です。
最後に、関連するアノテーションは @Startup アノテーションです。 それはアプリケーションやセッションスコープコンポーネントで利用可能です。 @Startup アノテーションは、 コンテキストが開始されたときにクライアントによる初めての参照を待つのではなく、 Seam に即座にコンポーネントをインスタンス化させさせます。 @Startup(depends={....}) を指定することで、 スタートアップコンポーネントのインスタンス化する順序の制御が可能です。
@Install アノテーションは、 特定のデプロイメントシナリオでは必須で別の場合はそうでないようなコンポーネントの条件付インストレーションを可能にします。 これは以下の場合に便利です。
テストで特定のインフラストラクチャのコンポーネントをモックとしたい。
特定のデプロイメントシナリオでコンポーネント実装を変更したい。
依存性が有効な場合だけに特定のコンポーネントをインストールしたい (フレームワークの作者に便利)。
@Install は works by letting you specify 優先順位 と 依存性 を指定することで動作します。
コンポーネントの優先順位は、 クラスパス中に同じコンポーネント名を持つ複数のクラスがある場合に、 インストールすべきコンポーネントを決定するために Seam が使用する番号です。 Seam はより優先湯順位が高いコンポーネントを選択します。 あらかじめ決められた優先順位の値があります (昇順)。
BUILT_IN — Seam に組み込まれた最も優先順位が低いコンポーネントです。
FRAMEWORK — サードパーティフレームワークによって定義されたコンポーネントは組み込みコンポーネントをオーバーライドする可能性がありますが、 アプリケーションによってオーバーライドされます。
APPLICATION — デフォルト優先順位、 これはほとんどのアプリケーションコンポーネントにおいて適切です。
DEPLOYMENT — デプロイメント固有のアプリケーションコンポーネント用です。
MOCK — テストに使用されるモックオブジェクト用です。
JMS キューと対話する messageSender という名前のコンポーネントがあるとすると、
@Name("messageSender") public class MessageSender { public void sendMessage() { //do something with JMS } }
ユニットテストで、 有効なJMS キューがないので、このメソッドを消してしまいたくなります。 ユニットテストが実行されるときにクラスパスに存在するけれどアプリケーションではデプロイされない mock コンポーネントを作成します。 application:
@Name("messageSender") @Install(precedence=MOCK) public class MockMessageSender extends MessageSender { public void sendMessage() { //do nothing! } }
優先順位 はクラスパスで両方のコンポーネントを発見したとき、 Seam がどちらのバージョンを使用するかを助けます。
クラスパスにある複数のクラスを正確に制御できるならば、 これはすばらしいことです。 しかし、 多くの依存性を持つ再利用可能なフレームワークを記述している場合、 多くの Jar 全体にそのフレームワークをブレークさせたいとは思わないでしょう。 他にどのようなコンポーネントがインストールされているか、 クラスパス中にどんなクラスが使用可能であるかに応じて、 インストールすべきコンポーネントを決める方法の方が好まれるはずです。 @Install アノテーションはこの機能も制御しています。 Seam は多くの組み込みコンポーネントの条件付きインストールを実現するために内部でこのメカニズムを使用します。 しかし、 ご使用のアプリケーションでは恐らく使用する必要がないでしょう。
こんなうっとうしいコードを見るのに飽き飽きしているのは誰ですか?
private static final Log log = LogFactory.getLog(CreateOrderAction.class); public Order createOrder(User user, Product product, int quantity) { if ( log.isDebugEnabled() ) { log.debug("Creating new order for user: " + user.username() + " product: " + product.name() + " quantity: " + quantity); } return new Order(user, product, quantity); }
簡単なログメッセージのためのコードをどうしてこんなに冗長にすることができるのか想像するのは困難です。 実際のビジネスロジックに関連するコード行よりロギングに関連する方がより多くあります。 Java コミュニティが 10 年の間もっと良いものを考え出せなかったことは本当に驚きです。
Seam はたくさんのコードを簡素化するロギング API を提供します。
@Logger private Log log; public Order createOrder(User user, Product product, int quantity) { log.debug("Creating new order for user: #0 product: #1 quantity: #2", user.username(), product.name(), quantity); return new Order(user, product, quantity); }
ログ 変数が静的であると宣言するかどうかは問題ではありません — ログ 変数が静的である必要があるエンティティ Bean コンポーネント以外なら、 どちらの方法でもうまくいくでしょう。
ストリング連結は、debug( メソッドの内部で起こるため、 うっとうしい if ( log.isDebugEnabled() ) による監視は不要であることに留意してください。 Seam はどのコンポーネントに Log をインジェクトしたかを知っているため、 通常、ログカテゴリを明示的に指定する必要ないことも留意してください。
User と Product が、 現在のコンテキストで有効な Seam コンポーネントの場合、それはさらに良くなります。
@Logger private Log log; public Order createOrder(User user, Product product, int quantity) { log.debug("Creating new order for user: #{user.username} product: #{product.name} quantity: #0", quantity); return new Order(user, product, quantity); }
Seam ロギングは自動的に log4j あるいは JDK logging に出力を送付するかを選択します。 log4j がクラスパスに通っていれば、Seam はそれを使用します。 そうでなければ、Seam は JDK logging を使用します。
アプリケーションが明示的に setAttribute() を呼び出すとときに、 セッションにバインドした可変オブジェクトの状態変化が複製されるだけなので、 多くのアプリケーションサーバの機能は驚くほどいい加減な HttpSession クラスタリングの実装を持っています。 これはフェイルオーバが発生するときにだけに現れるので、 効果的に開発時間でテストされることができないバグの原因です。 さらに実際の複製メッセージはセッション属性とバインドしたシリアライズされたオブジェクトグラフ全体を含んでいます。 そして、それは非効率です。
もちろん、EJB ステートフルセッション Bean は自動的にダーティなチェックを実行が必要であり、 可変状態の複製と洗練された EJB コンテナは属性レベルの複製など最適化が行うことが可能です。 あいにく、すべての Seam ユーザが EJB 3.0 をサポートする恵まれた環境で作業をしているわけではありません。 そこで、セッションと対話スコープの JavaBean とエンティティ Bean コンポーネントのために、 Seam は Web コンテナセッションクラスタリングの上でクラスタセーフな状態管理の特別なレイヤを提供します。
セッションや対話スコープの JavaBean コンポーネントのために、Seam は、 コンポーネントがアプリケーションにより呼び出されるリクエストの毎に、 setAttribute() を呼ぶことにより自動的に複製を命じます。 もちろん、このストラテジは読み取りばかりするコンポーネントでは不十分です。 この振る舞いは、 org.jboss.seam.core.Mutable インタフェースを実装するか、 org.jboss.seam.core.AbstractMutable を拡張するか、 あるいは、コンポーネント中に独自のダーティチェックのロジックを記述するかにより制御可能です。 以下に例を示します。
@Name("account") public class Account extends AbstractMutable { private BigDecimal balance; public void setBalance(BigDecimal balance) { setDirty(this.balance, balance); this.balance = balance; } public BigDecimal getBalance() { return balance; } ... }
あるいは、同様の効果を得るために @ReadOnly アノテーションの使用も可能です。
@Name("account") public class Account { private BigDecimal balance; public void setBalance(BigDecimal balance) { this.balance = balance; } @ReadOnly public BigDecimal getBalance() { return balance; } ... }
セッションや対話スコープのエンティティ Bean コンポーネントの場合、 Seam は 複製が不要な場合、 ( 対話スコープの) エンティティが現在の Seam 管理の永続性コンテキストに関連付けられている限り、 リクエスト毎に setAttribute() を呼ぶことにより自動的に複製の作成を強制します。 このストラテジは必ずしも効率的ではないので、 セッションや対話スコープエンティティ Bean は注意して使用してください。 エンティティ Bean インスタンスを「管理」するために、 ステートフルセッション Bean や JavaBean をいつでも記述することができます。 以下に例を示します。
@Stateful @Name("account") public class AccountManager extends AbstractMutable { private Account account; // an entity bean @Unwrap public void getAccount() { return account; } ... }
Seam アプリケーションフレームワークにおいて、 EntityHome クラスはこのパターンの優れたサンプルを提供しすることに留意してください。
Seam コンポーネントではないオブジェクトと連携することもしばしばあります。 でも、やはり @In を使用して Seam コンポーネントにインジェクトし、 値やメソッドバインディング式などでそれらを使いたいと思うことがあります。 時には、それを Seam コンテキストのライフサイクルに関連付ける必要さえあります (例えば @Destroy)。 そこで、Seam コンテキストは Seam ではないいオブジェクトを含むことが可能で、 Seam は、コンテキストにバインドする非コンポーネントと連携することを容易にする 2、3 の優れた機能を提供します。
ファクトリコンポーネントパターン は、 Seam コンポーネントを非コンポーネントオブジェクト用のインスタンス化する機能として動作させます。 ファクトリメソッド は、 コンテキスト変数が参照されたときに呼び出されますが、 それとバインドした値は持っていません。 @Factory アノテーションを使用してファクトリメソッドを定義します。 ファクトリメソッドは値をコンテキスト変数とバインドし、 バインドされた値のスコープを決定します。 2 種類のファクトリメソッドスタイルがあります。 最初のスタイルは、Seam によりコンテキストにバインドされた値を返します。
@Factory(scope=CONVERSATION) public List<Customer> getCustomerList() { return ... ; }
2 番目のスタイルは、 値をコンテキスト変数そのものにバインドした void タイプのメソッドです。
@DataModel List<Customer> customerList; @Factory("customerList") public void initCustomerList() { customerList = ... ; }
どちらの場合も、 customerList コンテキスト変数を参照してその値が null になり、 その値のライフサイクルで行うことがこれ以上ない場合、 ファクトリメソッドが呼ばれます。 さらに強力なパターンは 管理コンポーネントパターン です。 この場合、 コンテキスト変数にバインドする Seam コンポーネントがあり、 このコンポーネントがコンテキスト変数の値を管理り、 残りはクライアントで見えない場合です。
管理コンポーネントは @Unwrap メソッドを持つすべてのコンポーネントです。 このメソッドは、クライアントに見えなくなる値を返し、 毎回 コンテキスト変数が参照されれば呼び出されます。
@Name("customerList") @Scope(CONVERSATION) public class CustomerListManager { ... @Unwrap public List<Customer> getCustomerList() { return ... ; } }
コンテキストが終了するとき、クリーンアップが必要な重いオブジェクトを持つとき、 このパターンは特に役立ちます。 この場合、管理コンポーネントは @Destroy でクリーンアップを実行することもできます。