SeamFramework.orgCommunity Documentation
Seam における 2 つの中心的概念は、 コンテキスト の概念と コンポーネント の概念です。 コンポーネントは、ステートフルなオブジェクト、通常は EJB です。 コンポーネントのインスタンスは、コンテキストと関連づけられ、そのコンテキスト中で名前を与えられます。 バイジェクション (Bijection) は、内部のコンポーネント名 (インスタンス変数) をコンテキスト中の名前にエイリアスし、 Seam によるコンポーネントツリーの動的な組み立て、再組み立てを可能にするメカニズムを提供します。
Seam に組み込まれたコンテキストから説明を始めましょう。
Seam コンテキストはフレームワークによって生成、破棄されます。 アプリケーションは Java API 呼び出しによってコンテキストの区分 (demarcation) を明示的に制御することはできません。 コンテキストは通常、暗黙的ですが、場合によってコンテキストはアノテーションによって区分されます。
基本の Seam コンテキストは以下の通りです。
ステートレスコンテキスト
Event (i.e., request) context
ページコンテキスト
対話コンテキスト
セッションコンテキスト
ビジネスプロセスコンテキスト
アプリケーションコンテキスト
これらのコンテキストのいくつかは、サーブレットや関連する仕様に由来していることがわかります。 しかし、このうち 2 つは目新しいかもしれません。 対話コンテキスト (conversation context) とビジネスプロセスコンテキストです。 Web アプリケーション中での状態管理がとても脆弱でエラーが発生しやすい 1 つの理由は、 3 つの組み込みコンテキスト (要求、セッション、アプリケーション) がビジネスロジックの観点から特定の意味を持たないからです。 例えば、実際のアプリケーションのワークフローの観点から見るとユーザーログインセッションは極めて自由裁量な構造です。 そのため、ほとんどの Seam コンポーネントは、対話コンテキストあるいはビジネスプロセスコンテキストのスコープに配置されます。 なぜなら、それらはアプリケーションの観点からとても意味のあるコンテキストだからです。
順に、それぞれのコンテキストを見ていきましょう。
Components which are truly stateless (stateless session beans, primarily) always live in the stateless context (which is basically the absense of a context since the instance Seam resolves is not stored). Stateless components are not very interesting, and are arguably not very object-oriented. Nevertheless, they do get developed and used and are thus an important part of any Seam application.
イベントコンテキストは「最も狭い」状態を持つコンテキストで、 他の種類のイベントを網羅する Web 要求コンテキストの概念の一般化です。 それにもかかわらず、JSF 要求のライフサイクルと関連づけられたイベントコンテキストは、 イベントコンテキストの最も重要な用例であり、最もよく利用されるものです。 要求終了時に、イベントコンテキストに関連するコンポーネントは破棄されますが、 それらの状態は、少なくとも要求のライフサイクルの間では有効かつ明確です。
RMI 経由あるいは Seam Remoting により Seam コンポーネントを呼び出すとき、 イベントコンテキストは、その呼び出しだけのために生成、破棄されます。
ページコンテキストは、レンダリングされたページの特定のインスタンスと状態との関連づけを可能にします。 イベントリスナー内で状態の初期化が可能で、 実際にページをレンダリングしている間に、 ページに由来するどんなイベントからも状態にアクセスが可能です。 これは特にサーバサイドのデータ変化にリストが連動しているクリッカブルリストのような機能に役立ちます。 状態は実際にはクライアントのためにシリアライズされます。 そのため、この構造は複数ウインドの操作や戻るボタンに対して極めて堅牢です。
対話コンテキストは Seam でまさに中心となるコンセプトです。 対話 (conversation) は、ユーザーの観点からの作業単位です。 それはユーザーとのインタラクション、要求、およびデータベーストランザクションをまたぐかもしれません。 しかし、ユーザーにとって対話は、1 つの問題を解決します。 例えば、「ホテル予約」、「契約承認」、「注文作成」はすべて対話です。 対話というものが 1 つの「ユースケース」あるいは「ユーザーストーリ」を実装していると考えたいかもしれませんが、関係は必ずしもその通りにはなりません。
対話は、「ユーザーがこのウィンドウの中で現在していること」と関連づけられた状態を保持します。 1 人のユーザーは、通常マルチウィンドウで、ある時点に進行中の複数の対話を持っているかもしれません。 対話コンテキストは、異なる対話からの状態の衝突をなくし、バグの原因とならないことを保証します。
対話の観点からアプリケーションについて考えることに慣れるには時間がかかるかもしれません。 しかし、慣れてしまうと、このコンセプトが大好きになり、もう対話なしでは考えられなくなるだろうと思います。
ある対話は単に 1 つの要求の間続いています。 複数の要求をまたぐ対話は、Seam によって提供されたアノテーションを使って、区分を示されなければなりません。
一部の対話は タスク でもあります。 タスクは長期ビジネスプロセスの観点では重要な意味を持つ対話であり、 タスクが首尾よく完了する場合、 ビジネスプロセスの状態遷移を引き起こす可能性を持っています。 Seam はタスク区分用に特別なアノテーションのセットを提供します。
より広い対話の "内部" で対話を発生させるような ネスト も可能です。 これは拡張機能です。
通常、実際には要求と要求の間サーブレットセッション中で Seam により対話状態は保持されます。 Seam は設定可能な 対話タイムアウト (conversation timeout) を実装し、 自動的に不活性な対話を破棄し、 ユーザーが対話を中断しても、ユーザーログインセッションにより保持される状態は際限なく増加しないことが保証されています。
Seam は同じプロセス中の同じ長期対話コンテキスト中で発生する並列の要求処理をシリアル化します。
あるいは、Seam はクライアントブラウザの中に対話の状態を保持するように設定される場合もあります。
セッションコンテキストはユーザーログインに関する状態を保持します。 いくつかの場合では対話の間で状態を共有することが有用なことがありますが、 ログインユーザーに関するグローバル情報以外のコンポーネントを保持するために、 セッションコンテキストを使用することは賛成できません。
JSR-168 ポータル環境では、セッションコンテキストはポートレットセッションを意味します。
ビジネスプロセスコンテキストは長期ビジネスプロセスに関する状態を保持します。 この状態は BPM エンジン (JBoss jBPM) によって管理や永続化が行われます。 ビジネスプロセスは、複数ユーザーの複数インタラクションを橋渡しします。 従って、この状態は複数ユーザーの間で明確な方法で共有されます。 現在のタスクは現在のビジネスプロセスインスタンスを決定し、 ビジネスプロセスのライフサイクルは プロセス定義言語 (process definition language) を使用することで外部に定義されます。 従って、ビジネスプロセスの区分のために特別なアノテーションはありません。
アプリケーションコンテキストはサーブレット仕様からおなじみのサーブレットのコンテキストです。 アプリケーションコンテキストは主に、設定データ、参照データ、メタモデルのような静的な情報を保持するために役立ちます。 例えば、Seam はアプリケーションコンテキスト内に Seam 設定やメタモデルを保管しています。
コンテキストは名前空間、コンテキスト変数 のセットを定義します。 これらはサーブレット仕様のセッションや要求属性と同様に機能します。 どのような値でもコンテキスト変数とバインドができますが、 通常、Seam コンポーネントインスタンスをコンテキスト変数にバインドします。
従って、コンテキスト中では、 コンポーネントインスタンスは、コンテキスト変数名 (いつもではないが通常はコンポーネント名と同じ) で識別されます。 Contexts
クラスを通して特定のスコープの指定されたコンポーネントインスタンスにプログラム的にアクセスもできます。 それは Context
インタフェースのスレッドに結びついたインスタンスへのアクセスを提供します。
User user = (User) Contexts.getSessionContext().get("user");
名前に関連する値を設定したり変更したりすることもできます。
Contexts.getSessionContext().set("user", user);
しかしながら、通常、インジェクションを通してコンテキストからコンポーネントを取得し、 アウトジェクションを通してコンポーネントインスタンスをコンテキストに配置します。
上記のように、コンポーネントインスタンスは特定の周知のスコープから取得することもありますが、 それ以外の場合、すべてのステートフルスコープは 優先順位 に従って検索されます。 その順序は以下の通りです。
イベントコンテキスト
ページコンテキスト
対話コンテキスト
セッションコンテキスト
ビジネスプロセスコンテキスト
アプリケーションコンテキスト
Contexts.lookupInStatefulContexts()
を呼び出すことによって優先順位の検索も可能です。 JSF ページから名前によってアクセスする場合はいつも、優先順位検索が発生します。
サーブレット仕様も EJB 仕様も同じクライアントから起こる同時並行の要求を管理するための仕組みをまったく定義していません。 サーブレットコンテナは単純にすべてのスレッドを同時並行的に稼動させ、 スレッドセーフとすることをアプリケーションコードに任せます。 EJB コンテナはステートレスコンポーネントが同時並行的にアクセスされることを可能にし、 複数のスレッドがひとつのステートレスセッション Bean にアクセスするならば例外をスローします。
この振る舞いは粒度の細かい同期要求をベースとする古いスタイルの Web アプリケーションでは大丈夫であったかもしれません。 しかし、多くの粒度の細かい非同期要求 (AJAX) を多用する最新のアプリケーションにとって、 同時並行はまぎれもない事実であり、プログラムモデルとしてサポートされなければなりません。 Seam は同時並行管理レイヤをコンテキストモデルに織り込みます。
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 entity beans (i.e., JPA entity classes)
JavaBeans
EJB 3.0 メッセージ駆動型 Bean
Spring beans (see 章 27. Spring Framework 統合)
ステートレスセッション Bean コンポーネントは、複数の呼出しに対して状態を保持することができません。 従って、それらは通常さまざまな Seam コンテキスト内の別コンポーネントの状態を操作するのに役に立ちます。 それらは JSF のアクションリスナーとして使用できるかもしれませんが、 表示のために JSF コンポーネントにプロパティを提供することはできません。
ステートレスセッション Bean はいつもステートレスコンテキストに置かれます。
新しいインスタンスが各要求で使用されるのと同様にステートレスセッション Bean は同時並行的にアクセスされることが可能です。 インスタンスを要求に割り当てることは EJB3 コンテナの責務です。 ( 通常インスタンスは再利用可能なプールから割り当てられます、 つまり、Bean の使用済みのものからデータを含むインスタンス変数を見つけることができることを意味します。)
ステートレスセッション Bean は最も興味のわかない種類の Seam コンポーネントです。
Seam ステートレスセッション Bean コンポーネントは Component.getInstance()
または @In(create=true)
を使用してインスタンス化可能です。これらは JNDI ルックアップや 直接 new オペレータでインスタンス化されるべきではありません。
ステートフルセッション Bean コンポーネントは、 Bean の複数の呼出しに対して状態を保持することができるだけでなく、 複数の要求に対して状態を保持することもできます。 データベースに保持されていないアプリケーションの状態は、 通常、ステートフルセッション Bean によって保持される必要があります。 これは Seam と他の多くの Web アプリケーションフレームワークとの大きな違いです。 現在の対話の情報を直接 HttpSession
に押し込める代わりに、 対話コンテキストに結びついたステートフルセッション Bean のインスタンス変数の中にそれを保持すべきです。 これは、Seam がこの状態のライフサイクルの管理を可能にし、 異なる同時実行中の対話に関連する状態の間に衝突がないことを保証します。
ステートフルセッション Bean はしばしば JSF アクションリスナー、または、 表示もしくはフォームのサブミットのためにプロパティを提供する JSF コンポーネントのバッキング Bean として使用されます。
デフォルトで、ステートフルセッション Bean は対話コンテキストとバインドします。 ページもしくはステートレスコンテキストとバインドできません。
セッションスコープのステートレスセッション Bean への同時並行要求は、 そのBeanへのSeamインタセプタが無効にされていない限り、常に Seam によってシリアライズされます。
Seam ステートフルセッション Bean コンポーネントは Component.getInstance()
または @In(create=true)
を使用してインスタンス化可能です。これらは JNDI ルックアップや 直接 new
オペレータでインスタンス化されるべきではありません。
エンティティ Bean はコンテキスト変数とバインドし、Seamコンポーネントとして機能することもあります。 エンティティは、コンテキスト依存識別子に加えて永続識別子を持つために、 エンティティのインスタンスは、Seam によって暗黙的にインスタンス化されるより、 むしろ Java コード中で明示的にバインドされます。
エンティティ Bean コンポーネントはバイジェクションもコンテキスト区分もサポートしません。 また、エンティティ Bean トリガのデータ妥当性検証の呼び出しもサポートしていません。
エンティティ Bean は、通常 JSF アクションリスナーとして使用されませんが、 しばしば、表示あるいはフォームのサブミットのために JSF コンポーネントにプロパティを提供するバッキング Bean として機能します。 特に、エンティティ Bean をバッキング Bean として使用することは一般的であり、 追加 / 変更 / 削除タイプの機能の実装のためのステートレスセッション Bean アクションリスナーと一緒に使用されます。
デフォルトで、エンティティ Bean は対話コンテキストとバインドします。 ステートレスセッション Bean とはバインドしません。
クラスタリングされた環境では、 ステートフルセッション Bean 中でエンティティ Bean の参照を保持することより、 エンティティ Bean を直接的に対話あるいはセッションスコープの Seam コンテキスト変数にバインドする方が多少非効率的であることに留意してください。 この理由のため、すべての Seam アプリケーションが Seam コンポーネントであるためにエンティティ Bean を定義するわけではありません。
Seam エンティティ Bean コンポーネントは Component.getInstance()
または @In(create=true)
を使用してインスタンス化可能です。あるいは、直接 new
オペレータを使用することが可能です。
JavaBean はステートレスあるいはステートフルセッション Bean のように使用されることもあります。 しかし、それらはセッション Bean の機能を提供していません。 (宣言的トランザクション区分、 宣言的セキュリティ、 効率的にクラスタ化された状態レプリケーション、 EJB 3.0 永続性、 タイムアウトメソッドなど)
後の章で、EJB コンテナなしで Seam や Hibernate を使用する方法を紹介しています。 このユースケースでは、コンポーネントはセッション Bean の代わりに JavaBean です。 しかし、多くのアプリケーションサーバーでは、 ステートフルセッション Bean コンポーネントをクラスタリングするより、 対話あるいはセッションスコープの Seam JavaBean コンポーネントをクラスタリングする方が多少非効率的であることに留意してください。
デフォルトで、JavaBean はイベントコンテキストとバインドします。
セッションスコープの JavaBean への同時並行要求はいつも Seam によりシリアライズされます。
Seam JavaBean コンポーネントは Component.getInstance()
または @In(create=true)
を使用してインスタンス化可能です。これらは直接 new
オペレータでインスタンス化されるべきではありません。
メッセージ駆動形 Bean は Seam コンポーネントとして機能することができます。 しかし、メッセージ駆動型 Bean は、他の Seam コンポーネントとまったく異なった形で呼び出されます。 コンテキスト変数を通じてそれらを呼び出す代わりに、 JMS キュー あるいは、トピックに送信されたメッセージを待ち受けます。
メッセージ駆動形 Bean は、Seam コンテキストとバインドできません。 また、それらの「呼び出し元」のセッションや対話状態にアクセスできません。 しかし、メッセージ駆動形 Bean は、バイジェクションと他の Seam の機能をサポートします。
メッセージ駆動型 Bean はアプリケーションによってインスタンス化されません。これらはメッセージを受信したときに EJB コンテナによってインスタンス化されます。
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 コンポーネント名 で、 EJB 標準で定義された他の名前との関連はありません。 しかし、Seam コンポーネント名はちょうど JSF 管理 Bean のように動作するため、 2 つのコンセプトは同一と考えることができます。
@Name
はコンポーネント名を定義する唯一の方法ではありませんが、 いつも、どこかで名前を指定する必要があります。 もしそうしないと、他の Seam アノテーションはどれも機能しないでしょう。
Whenever Seam instantiates a component, it binds the new instance to a variable in the scope configured for the component that matches the component name. This behavior is identical to how JSF managed beans work, except that Seam allows you to configure this mapping using annotations rather than XML. You can also programmatically bind a component to a context variable. This is useful if a particular component serves more than one role in the system. For example, the currently logged in User
might be bound to the currentUser
session context variable, while a User
that is the subject of some administration functionality might be bound to the user
conversation context variable. Be careful, though, because through a programmatic assignment, it's possible to overwrite a context variable that has a reference to a Seam component, potentially confusing matters.
For very large applications, and for built-in seam components, qualified component names are often used to avoid naming conflicts.
@Name("com.jboss.myapp.loginAction")
@Stateless
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の名前空間をインポートする機能によって非修飾名でもアクセスすることができます。Seam JARに含まれる components.xml
ファイルは以下の名前空間を定義します。
<components xmlns="http://jboss.com/products/seam/components"> <import>org.jboss.seam.core</import> <import>org.jboss.seam.cache</import> <import>org.jboss.seam.transaction</import> <import>org.jboss.seam.framework</import> <import>org.jboss.seam.web</import> <import>org.jboss.seam.faces</import> <import>org.jboss.seam.international</import> <import>org.jboss.seam.theme</import> <import>org.jboss.seam.pageflow</import> <import>org.jboss.seam.bpm</import> <import>org.jboss.seam.jms</import> <import>org.jboss.seam.mail</import> <import>org.jboss.seam.security</import> <import>org.jboss.seam.security.management</import> <import>org.jboss.seam.security.permission</import> <import>org.jboss.seam.captcha</import> <import>org.jboss.seam.excel.exporter</import> <!-- ... ---> </components>
修飾された名前を解決するときは、Seamは順にそれぞれの名前空間を調べます。アプリケーション固有の名前空間のためにはアプリケーションのcomponents.xml
ファイルに追加する名前空間を含めます。
@Scope
アノテーションを使用して、コンポーネントのデフォルトスコープ (コンテキスト) をオーバーライドすることができます。 これにより Seam によってインスタンス化される時に、 コンポーネントインスタンスがどんなコンテキストと結合するかを定義ができます。
@Name("user")
@Entity
@Scope(SESSION)
public class User {
...
}
org.jboss.seam.ScopeType
は、可能なスコープの列挙を定義します。
Some Seam component classes can fulfill more than one role in the system. For example, we often have a User
class which is usually used as a session-scoped component representing the current user but is used in user administration screens as a conversation-scoped component. The @Role
annotation lets us define an additional named role for a component, with a different scope — it lets us bind the same component class to different context variables. (Any Seam component instance may be bound to multiple context variables, but this lets us do it at the class level, and take advantage of auto-instantiation.)
@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}!");
依存性の注入 (dependency injection) あるいは 制御の逆転 (inversion of control) は今ではもう大多数の Java 開発者によく知られた概念です。 依存性の注入はあるコンポーネントが他のコンポーネントの参照を持つのを可能にします。 それはコンテナによって setter メソッドあるいはインスタンス変数に他のコンポーネントを「インジェクト (注入)」させることで実現します。 これまであったすべての依存性の注入の実装では、 インジェクションはコンポーネントが生成されたときに起こり、 その後、参照はコンポーネントのライフサイクルの間で変化しません。 ステートレスコンポーネントにおいて、これは理にかなっています。 クライアントの観点から、特定のステートレスなコンポーネントのすべてのインスタンスは交換可能です。 一方、Seamはステートフルなコンポーネントの使用に重点を置いています。 従って、典型的な依存性の注入はもはやあまり有用な構造ではありません。 Seam はインジェクションの一般化として、バイジェクション (bijection) の概念を導入しました。 インジェクションと対比すると、バイジェクションは以下のようになります。
コンテキスト依存 (contextual) - バイジェクションはさまざまな異なるコンテキストからステートフルなコンポーネントを組み立てるために使用されます。 (「より広い (wider) 」コンテキストからのコンポーネントは「より狭い (narrow) 」コンテキストからの参照も持つかもしれません。)
双方向性 (bidirectional) - 値はコンテキスト変数から呼ばれるコンポーネントの属性にインジェクトされ、 また、コンポーネント属性からコンテキストにアウトジェクト (outject) され戻されます。 インスタンス変数そのものを設定することで、呼ばれたコンポーネントが簡単にコンテキスト変数の値を操作することを可能にします。
動的 (dynamic) - バイジェクションはコンポーネントが呼ばれるたびに発生します。 なぜなら、コンテキストの値は時間経過で変化し、 Seam コンポーネントがステートフルだからです。
本質的に、インスタンス変数の値をインジェクト、アウトジェクト、両方により指定することで、 バイジェクションはコンテキスト変数をコンポーネントのインスタンス変数にエイリアスを可能にします もちろん、バイジェクションを可能にするためにアノテーションが使用されています。
@In
アノテーションは値がインスタンス変数にインジェクトされることを指定しています。
@Name("loginAction")
@Stateless
public class LoginAction implements Login {
@In User user;
...
}
あるいは、setter メソッドにインジェクトされます。
@Name("loginAction")
@Stateless
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
public class LoginAction implements Login {
@In("#{user.username}") String username;
...
}
Injected values are disinjected (i.e., set to null
) immediately after method completion and outjection.
(コンポーネントライフサイクルとインジェクションについては次章により多くの情報があります。)
@Out
アノテーションは、属性がインスタンス変数からもアウトジェクトされるべきことを指定します。
@Name("loginAction")
@Stateless
public class LoginAction implements Login {
@Out User user;
...
}
あるいは getter メソッドからアウトジェクトされます。
@Name("loginAction")
@Stateless
public class LoginAction implements Login {
User user;
@Out
public User getUser() {
return user;
}
...
}
属性値はインジェクトされることもアウトジェクトされることも可能です。
@Name("loginAction")
@Stateless
public class LoginAction implements Login {
@In @Out User user;
...
}
または、
@Name("loginAction")
@Stateless
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 コンポーネントは通常の EJB 3.0 のライフサイクルのコールバック (@PostConstruct
、@PreDestroy
など) のすべてをサポートしています。 しかも、Seam は JavaBean コンポーネント でのこれらコールバックの使用もサポートしています。 しかし、これらのアノテーションは J2EE 環境では有効とならないため、Seam は @PostConstruct
と @PreDestroy
と等価な 2 つの追加コンポーネントライフサイクルコールバックを定義しています。
@Create
メソッドは Seam がコンポーネントをインスタンス化した後に呼ばれます。 コンポーネントは 1 つの @Create
メソッドのみ定義可能です。
@Destroy
メソッドは Seam コンポーネントがバインドするコンテキストが終了するときに呼ばれます。 コンポーネントは 1 つの @Destroy
メソッドのみ定義可能です。
さらに、ステートフルセッション Bean コンポーネントはパラメータ無しの@Remove
を付けることが必須 です。このメソッドはコンテキストが終了するときに Seam により呼ばれます。
最後に、関連するアノテーションは @Startup
アノテーションです。 それはアプリケーションやセッションスコープコンポーネントで利用可能です。 @Startup
アノテーションは、 コンテキストが開始されたときにクライアントによる初めての参照を待つのではなく、 Seam に即座にコンポーネントをインスタンス化させさせます。 @Startup(depends={....})
を指定することで、 スタートアップコンポーネントのインスタンス化する順序の制御が可能です。
@Install
アノテーションは、 特定のデプロイメントシナリオでは必須で別の場合はそうでないようなコンポーネントの条件付インストレーションを可能にします。 これは以下の場合に便利です。
テストで特定のインフラストラクチャのコンポーネントをモックとしたい。
特定のデプロイメントシナリオでコンポーネント実装を変更したい。
依存性が有効な場合だけに特定のコンポーネントをインストールしたい (フレームワークの作者に便利)。
@Install
は 優先順位 と 依存性 を指定することで動作します。
コンポーネントの優先順位は、 クラスパス中に同じコンポーネント名を持つ複数のクラスがある場合に、 インストールすべきコンポーネントを決定するために 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 コンポーネントを作成します。
@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);
}
It doesn't matter if you declare the log
variable static or not — it will work either way, except for entity bean components which require the log
variable to be static.
ストリング連結は、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 Account getAccount()
{
return account;
}
...
}
Seam アプリケーションフレームワークにおいて EntityHome
クラスは Seam コンポーネントを使用することでエンティティ Bean インスタンスを管理する優れたサンプルを提供していることに留意してください。
Seam コンポーネントではないオブジェクトと連携することもしばしばあります。 しかし、やはり @In
を使用して Seam コンポーネントにインジェクトし、 値やメソッドのバインディング式などでそれらを使いたいと思うことがあります。 時には、それを Seam コンテキストのライフサイクルに関連付ける必要さえあります (例えば @Destroy
)。 そこで、Seam コンテキストは Seam コンポーネントではないオブジェクトを含むことが可能で、 Seam は、コンテキストにバインドする非コンポーネントと連携することを容易にする 2、3 の優れた機能を提供します。
ファクトリコンポーネントパターン は、 Seam コンポーネントをコンポーネントではないオブジェクトのインスタンス化する機能として動作させます。 ファクトリメソッド は、 コンテキスト変数が参照されたときに呼び出されますが、 それとバインドした値は持っていません。 @Factory
アノテーションを使用してファクトリメソッドを定義します。 ファクトリメソッドは値をコンテキスト変数とバインドし、 バインドされた値のスコープを決定します。 二種類のファクトリメソッドスタイルがあります。 最初のスタイルは、Seam によりコンテキストにバインドされた値を返します。
@Factory(scope=CONVERSATION)
public List<Customer
> getCustomerList() {
return ... ;
}
二番目のスタイルは、 値をコンテキスト変数そのものにバインドした 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 ... ;
}
}
マネージャコンポーネントパターンはコンポーネントのライフサイクルでより制御を必要とする場面でオブジェクトがあるとき特に有用です。 例えば、コンテキスト終了時にクリーンアップを必要とする重量級のオブジェクトがあるとき、オブジェクトを @Unwrap
し、マネージャコンポーネントの @Destroy
メソッドでクリーンアップすることが可能です。
@Name("hens")
@Scope(APPLICATION)
public class HenHouse
{
Set<Hen> hens;
@In(required=false) Hen hen;
@Unwrap
public List<Hen> getHens()
{
if (hens == null)
{
// Setup our hens
}
return hens;
}
@Observer({"chickBorn", "chickenBoughtAtMarket"})
public addHen()
{
hens.add(hen);
}
@Observer("chickenSoldAtMarket")
public removeHen()
{
hens.remove(hen);
}
@Observer("foxGetsIn")
public removeAllHens()
{
hens.clear();
}
...
}
ここでは管理コンポーネントが基礎をなすオブジェクトの多くのイベント監視をしています。 コンポーネントはこれらのアクションそのものを管理し、オブジェクトはアクセスごとにアンラップされるために一貫性のあるビューが提供されます。